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

  1. <?php 
  2. /** 
  3. * Upgrade DB model. 
  4. * 
  5. * Manages DB upgrading. 
  6. * 
  7. * IMPORTANT: Make sure that the snapshot_data() function is up-to-date! 
  8. * Things that are missed during back-up might be lost forever... 
  9. * 
  10. * @since 1.0.0 
  11. * 
  12. * @package Membership2 
  13. * @subpackage Model 
  14. */ 
  15. class MS_Model_Upgrade extends MS_Model { 
  16.  
  17. /** 
  18. * Initialize upgrading check. 
  19. * 
  20. * @since 1.0.0 
  21. */ 
  22. public static function init() { 
  23. self::update(); 
  24.  
  25. MS_Factory::load( 'MS_Model_Upgrade' ); 
  26.  
  27. // This function is intended for development/testing only! 
  28. self::maybe_restore(); 
  29.  
  30. // This is a hidden feature available in the Settings > General page. 
  31. add_action( 'init', array( __CLASS__, 'maybe_reset' ) ); 
  32.  
  33. // Prevent WordPress from updating the Membership plugin when the 
  34. // WPMU DEV Dashboard is disabled. 
  35.  
  36. // PRO ONLY! 
  37.  
  38. do_action( 'ms_model_upgrade_init' ); 
  39.  
  40. /** 
  41. * Upgrade database. 
  42. * 
  43. * @since 1.0.0 
  44. * @param bool $force Also execute update logic when version did not change. 
  45. */ 
  46. public static function update( $force = false ) { 
  47. static $Done = false; 
  48.  
  49. if ( $Done && ! $force ) { return; } 
  50.  
  51. // Migration handler has its own valid_user check. 
  52. self::check_migration_handler(); 
  53.  
  54. // Updates are only triggered from Admin-Side by an Admin user. 
  55. if ( ! self::valid_user() ) { return; } 
  56.  
  57. // Check for correct network-wide protection setup. 
  58. self::check_settings(); 
  59.  
  60. $settings = MS_Factory::load( 'MS_Model_Settings' ); 
  61. $old_version = $settings->version; // Old: The version in DB. 
  62. $new_version = MS_Plugin::instance()->version; // New: Version in file. 
  63.  
  64. $is_new_setup = empty( $old_version ); 
  65.  
  66. // Compare current src version to DB version: 
  67. // We only do UP-grades but no DOWN-grades! 
  68. if ( $old_version ) { 
  69. $version_changed = version_compare( $old_version, $new_version, 'lt' ); 
  70. } else { 
  71. $version_changed = true; 
  72.  
  73. if ( $force || $version_changed ) { 
  74. $Done = true; 
  75. $msg = array(); 
  76.  
  77. /** 
  78. * ----- General update logic, executed on every update ------------ 
  79. */ 
  80.  
  81. do_action( 
  82. 'ms_model_upgrade_before_update',  
  83. $settings,  
  84. $old_version,  
  85. $new_version,  
  86. $force 
  87. ); 
  88.  
  89. // Prepare the Update message. 
  90. if ( ! $version_changed ) { 
  91. $msg[] = sprintf( 
  92. __( '<strong>Membership 2</strong> is set up for version %1$s!' , 'membership2' ),  
  93. $new_version 
  94. ); 
  95. } else { 
  96. $msg[] = sprintf( 
  97. __( '<strong>Membership 2</strong> was updated to version %1$s!' , 'membership2' ),  
  98. $new_version 
  99. ); 
  100.  
  101. // Every time the plugin is updated we clear the cache. 
  102. MS_Factory::clear(); 
  103.  
  104. // Create missing Membership pages. 
  105. $new_pages = MS_Model_Pages::create_missing_pages(); 
  106.  
  107. if ( ! empty( $new_pages ) ) { 
  108. $msg[] = sprintf( 
  109. __( 'New Membership pages created: "%1$s".', 'membership2' ),  
  110. implode( '", "', $new_pages ) 
  111. ); 
  112.  
  113. // Remove an old version of Protected Content 
  114. // TODO: REMOVE THIS BLOCK/FUNCTION END OF 2015 
  115. if ( $version_changed ) { 
  116. self::remove_old_copy(); 
  117.  
  118. // Note: We do not create menu items on upgrade! Users might have 
  119. // intentionally removed the items from the menu... 
  120.  
  121. /** 
  122. * ----- Version-Specific update logic ----------------------------- 
  123. */ 
  124.  
  125. // Upgrade from a 1.0.0.x version to 1.0.1.0 or higher 
  126. if ( version_compare( $old_version, '4.0.0.4', 'lt' ) ) { 
  127. self::_upgrade_1_0_1_0(); 
  128.  
  129. // Upgrade from 1.0.1.0 version to 1.0.1.1 or higher 
  130. // ONLY RELEVANT FOR PRO VERSION! 
  131.  
  132. // Upgrade from 1.0.1.x version to 1.0.2.0 or higher 
  133. if ( version_compare( $old_version, '4.0.0.6', 'lt' ) ) { 
  134. self::_upgrade_1_0_2_0(); 
  135.  
  136. /** 
  137. * ----- General update logic, executed on every update ------------ 
  138. */ 
  139.  
  140. $settings->version = $new_version; 
  141. $settings->save(); 
  142.  
  143. // Display a message after the page is reloaded. 
  144. if ( ! $is_new_setup ) { 
  145. lib3()->ui->admin_message( implode( '<br>', $msg ), '', '', 'ms-update' ); 
  146.  
  147. do_action( 
  148. 'ms_model_upgrade_after_update',  
  149. $settings,  
  150. $old_version,  
  151. $new_version,  
  152. $force 
  153. ); 
  154.  
  155. $addons = MS_Factory::load( 'MS_Model_Addon' ); 
  156. $addons->flush_list(); 
  157.  
  158. // This will reload the current page. 
  159. MS_Plugin::flush_rewrite_rules(); 
  160.  
  161. /** 
  162. * PRO ONLY! 
  163. * 
  164. * @since 1.0.1.2 
  165. * @param mixed $res False: Update plugin / True: WP ignores plugin. 
  166. * @param string $action 
  167. * @param object $args 
  168. * @return mixed 
  169. */ 
  170. static public function no_dash_plugins_api( $res, $action, $args ) { 
  171. // PRO ONLY! 
  172.  
  173. /** 
  174. * PRO ONLY! 
  175. * 
  176. * @since 1.0.1.2 
  177. * @param object $data 
  178. * @return object 
  179. */ 
  180. static public function no_dash_update_plugins( $data ) { 
  181. // PRO ONLY! 
  182.  
  183.  
  184. # ########################################################################## 
  185. # ########################################################################## 
  186.  
  187. /** 
  188. * Upgrade from any 1.0.0.x version to a higher version. 
  189. */ 
  190. static private function _upgrade_1_0_1_0() { 
  191. lib3()->updates->clear(); 
  192.  
  193. /** 
  194. * The "is_member" flag of users was not correctly saved when a 
  195. * subscription was added via the M2 > Members page. 
  196. * Fix this now. 
  197. */ 
  198. global $wpdb; 
  199. $sql = " 
  200. SELECT DISTINCT usr.user_id 
  201. FROM 
  202. {$wpdb->posts} post 
  203. LEFT JOIN {$wpdb->usermeta} usr 
  204. ON usr.user_id = post.post_author 
  205. AND usr.meta_key = 'ms_is_member' 
  206. WHERE 
  207. post_type = 'ms_relationship' 
  208. AND (usr.meta_value IS NULL OR usr.meta_value != 1); 
  209. "; 
  210. $result = $wpdb->get_col( $sql ); 
  211. foreach ( $result as $user_id ) { 
  212. lib3()->updates->add( 'update_user_meta', $user_id, 'ms_is_member', true ); 
  213.  
  214. // Execute all queued actions! 
  215. lib3()->updates->plugin( 'membership2' ); 
  216. lib3()->updates->execute(); 
  217.  
  218. /** 
  219. * Upgrade from 1.0.1.0 version to a higher version. 
  220. * ONLY RELEVANT FOR PRO VERSION! 
  221. */ 
  222. static private function _upgrade_1_0_1_1() { 
  223.  
  224. /** 
  225. * Upgrade from 1.0.1.1 version to a higher version. 
  226. */ 
  227. static private function _upgrade_1_0_2_0() { 
  228. lib3()->updates->clear(); 
  229.  
  230. /** 
  231. * Transaction logs are a bit messed up because some meta-keys have an 
  232. * underscore as prefix while other do not have it. This query removes 
  233. * the underscore from all transaction-log meta key names. 
  234. */ 
  235. global $wpdb; 
  236. $sql = " 
  237. UPDATE {$wpdb->postmeta} 
  238. INNER JOIN {$wpdb->posts} ON {$wpdb->postmeta}.post_id={$wpdb->posts}.ID 
  239. SET meta_key = SUBSTR(meta_key, 2) 
  240. WHERE 
  241. {$wpdb->posts}.post_type = 'ms_transaction_log' 
  242. AND SUBSTR({$wpdb->postmeta}.meta_key, 1, 1) = '_' 
  243. "; 
  244. $ids = $wpdb->query( $sql ); 
  245.  
  246. # ########################################################################## 
  247.  
  248. /** 
  249. * Used when upgrading from Membership to M2. If both Membership and 
  250. * Protected Content are installed when upgrading then the old 
  251. * "protected-content" folder may survive the upgrade and needs to be 
  252. * manually removed. 
  253. * 
  254. * @since 1.0.0 
  255. */ 
  256. static private function remove_old_copy() { 
  257. $new_dir = WP_PLUGIN_DIR . '/membership'; 
  258. // ONLY RELEVANT IN PRO VERSION! 
  259. return; 
  260.  
  261. # ########################################################################## 
  262. # ########################################################################## 
  263.  
  264.  
  265. /** 
  266. * Completely whipe all Membership data from Database. 
  267. * 
  268. * Note: This function is not used currently... 
  269. * 
  270. * @since 1.0.0 
  271. */ 
  272. static private function cleanup_db() { 
  273. global $wpdb; 
  274. $sql = array(); 
  275. $trash_ids = array(); 
  276.  
  277. // Delete membership meta-data from users. 
  278. $users = MS_Model_Member::get_members(); 
  279. foreach ( $users as $user ) { 
  280. $user->delete_all_membership_usermeta(); 
  281. $user->save(); 
  282.  
  283. // Determine IDs of Membership Pages. 
  284. $page_types = MS_Model_Pages::get_page_types(); 
  285. foreach ( $page_types as $type => $name ) { 
  286. $page_id = MS_Model_Pages::get_setting( $type ); 
  287. $trash_ids[] = $page_id; 
  288.  
  289. /** 
  290. * Delete all plugin settings. 
  291. * Settings are saved by classes that extend MS_Model_option 
  292. */ 
  293. foreach ( MS_Model_Gateway::get_gateways() as $option ) { 
  294. $option->delete(); 
  295. MS_Factory::load( 'MS_Model_Addon' )->delete(); 
  296. MS_Factory::load( 'MS_Model_Pages' )->delete(); 
  297. MS_Factory::load( 'MS_Model_Settings' )->delete(); 
  298.  
  299. /** 
  300. * Delete transient data 
  301. * Transient data is saved by classed that extend MS_Model_Transient 
  302. */ 
  303. MS_Factory::load( 'MS_Model_Simulate' )->delete(); 
  304.  
  305. /** 
  306. * Delete all plugin content. 
  307. * Content is saved by classes that extend MS_Model_CustomPostType 
  308. */ 
  309. $ms_posttypes = array( 
  310. MS_Model_Communication::get_post_type(),  
  311. MS_Model_Event::get_post_type(),  
  312. MS_Model_Invoice::get_post_type(),  
  313. MS_Model_Transactionlog::get_post_type(),  
  314. MS_Model_Membership::get_post_type(),  
  315. MS_Model_Relationship::get_post_type(),  
  316. // MS_Addon_Coupon_Model::get_post_type(), ONLY PRO 
  317. // MS_Addon_Invitation_Model::get_post_type(), ONLY PRO 
  318. ); 
  319.  
  320. foreach ( $ms_posttypes as $type ) { 
  321. $sql[] = $wpdb->prepare( 
  322. "DELETE FROM $wpdb->posts WHERE post_type = %s;",  
  323. $type 
  324. ); 
  325.  
  326. // Remove orphaned post-metadata. 
  327. $sql[] = " 
  328. DELETE FROM $wpdb->postmeta 
  329. WHERE NOT EXISTS ( 
  330. SELECT 1 FROM $wpdb->posts tmp WHERE tmp.ID = post_id 
  331. ); 
  332. "; 
  333.  
  334. // Clear all WP transient cache. 
  335. $sql[] = " 
  336. DELETE FROM $wpdb->options 
  337. WHERE option_name LIKE '_transient_%'; 
  338. "; 
  339.  
  340. foreach ( $sql as $s ) { 
  341. $wpdb->query( $s ); 
  342.  
  343. // Move Membership pages to trash. 
  344. foreach ( $trash_ids as $id ) { 
  345. wp_delete_post( $id, true ); 
  346.  
  347. // Clear all data from WP Object cache. 
  348. wp_cache_flush(); 
  349.  
  350. // Redirect to the main page. 
  351. wp_safe_redirect( MS_Controller_Plugin::get_admin_url() ); 
  352. exit; 
  353.  
  354. /** 
  355. * Checks several settings to make sure that M2 is fully working. 
  356. * 
  357. * A) Makes sure that network-wide protection works by ensuring that the 
  358. * plugin is also network-activated. 
  359. * B) Checks if the permalink structure uses the post-name 
  360. * 
  361. * @since 1.0.0 
  362. */ 
  363. static private function check_settings() { 
  364. static $Setting_Check_Done = false; 
  365.  
  366. if ( ! $Setting_Check_Done ) { 
  367. $Setting_Check_Done = true; 
  368.  
  369. // A) ONLY RELEVANT IN PRO VERSION! 
  370.  
  371. // B) Check the Permalink settings. 
  372. if ( false === strpos( get_option( 'permalink_structure' ), '%postname%' ) ) { 
  373. lib3()->ui->admin_message( 
  374. sprintf( 
  375. __( 'Your %sPermalink structure%s should include the %sPost name%s to ensure Membership 2 is working correctly.', 'membership2' ),  
  376. '<a href="' . admin_url( 'options-permalink.php' ) . '">',  
  377. '</a>',  
  378. '<strong>',  
  379. '</strong>' 
  380. ),  
  381. 'err' 
  382. ); 
  383.  
  384. return; 
  385.  
  386. /** 
  387. * This function checks if we arrive here after a migration, i.e. after the 
  388. * user updated Membership Premium or Protected Content to M2 
  389. * 
  390. * @since 1.0.0 
  391. */ 
  392. static private function check_migration_handler() { 
  393. $migrate = ''; 
  394. $settings = MS_Factory::load( 'MS_Model_Settings' ); 
  395.  
  396. // Check Migration from old Membership plugin. 
  397. $option_m1 = '_wpmudev_update_to_m2'; 
  398. $option_m1_free = '_wporg_update_to_m2'; 
  399. $from_m1 = get_site_option( $option_m1 ); 
  400. $from_m1_free = get_site_option( $option_m1_free ); 
  401.  
  402. if ( $from_m1 || $from_m1_free ) { 
  403. $migrate = 'm1'; 
  404.  
  405. delete_site_option( $option_m1 ); 
  406. delete_site_option( $option_m1_free ); 
  407. $settings->set_special_view( 'MS_View_MigrationM1' ); 
  408.  
  409. $view = $settings->get_special_view(); 
  410.  
  411. if ( $migrate || 'MS_View_MigrationM1' == $view ) { 
  412. if ( ! empty( $_REQUEST['skip_import'] ) ) { 
  413. $settings->reset_special_view(); 
  414. wp_safe_redirect( 
  415. esc_url_raw( remove_query_arg( array( 'skip_import' ) ) ) 
  416. ); 
  417. exit; 
  418. } else { 
  419. $settings->set_special_view( 'MS_View_MigrationM1' ); 
  420.  
  421. // Complete the migration when the import is done. 
  422. add_action( 
  423. 'ms_import_action_done',  
  424. array( 'MS_Model_Settings', 'reset_special_view' ) 
  425. ); 
  426.  
  427. /** 
  428. * Returns a secure token to trigger advanced admin actions like db-reset 
  429. * or restoring a snapshot. 
  430. * 
  431. * - Only one token is valid at any given time. 
  432. * - Each token has a timeout of max. 120 seconds. 
  433. * - Each token can be used once only. 
  434. * 
  435. * @since 1.0.0 
  436. * @internal 
  437. * 
  438. * @param string $action Like a nonce, this is the action to execute. 
  439. * @return array Intended usage: add_query_param( $token, $url ) 
  440. */ 
  441. static public function get_token( $action ) { 
  442. if ( ! is_user_logged_in() ) { return array(); } 
  443. if ( ! is_admin() ) { return array(); } 
  444.  
  445. $one_time_key = uniqid(); 
  446. MS_Factory::set_transient( 'ms_one_time_key-' . $action, $one_time_key, 120 ); 
  447.  
  448. // Token is valid for 86 seconds because of usage of date('B') 
  449. $plain = $action . '-' . date( 'B' ) . ':' . get_current_user_id() . '-' . $one_time_key; 
  450. $token = array( 'ms_token' => wp_create_nonce( $plain ) ); 
  451. return $token; 
  452.  
  453. /** 
  454. * Verfies the admin token in the $_GET collection 
  455. * 
  456. * $_GET['ms_token'] must match the current ms_token 
  457. * $_POST['confirm'] must have value 'yes' 
  458. * 
  459. * @since 1.0.0 
  460. * @internal 
  461. * 
  462. * @param string $action Like a nonce, this is the action to execute. 
  463. * @return bool 
  464. */ 
  465. static private function verify_token( $action ) { 
  466. if ( ! self::valid_user() ) { return false; } 
  467.  
  468. if ( empty( $_GET['ms_token'] ) ) { return false; } 
  469. $get_token = $_GET['ms_token']; 
  470.  
  471. if ( empty( $_POST['confirm'] ) ) { return false; } 
  472. if ( 'yes' != $_POST['confirm'] ) { return false; } 
  473.  
  474. $one_time_key = MS_Factory::get_transient( 'ms_one_time_key-' . $action ); 
  475. MS_Factory::delete_transient( 'ms_one_time_key-' . $action ); 
  476. if ( empty( $one_time_key ) ) { return false; } 
  477.  
  478. // We verify the current and the previous beat 
  479. $plain_token_1 = $action . '-' . date( 'B' ) . ':' . get_current_user_id() . '-' . $one_time_key; 
  480. $plain_token_2 = $action . '-' . ( date( 'B' ) - 1 ) . ':' . get_current_user_id() . '-' . $one_time_key; 
  481.  
  482. if ( wp_verify_nonce( $get_token, $plain_token_1 ) ) { return true; } 
  483. if ( wp_verify_nonce( $get_token, $plain_token_2 ) ) { return true; } 
  484.  
  485. return false; 
  486.  
  487. /** 
  488. * Verifies the following conditions: 
  489. * - Current user is logged in and has admin permissions 
  490. * - The request is an wp-admin request 
  491. * - The request is not an Ajax call 
  492. * 
  493. * @since 1.0.0 
  494. * @return bool True if all conditions are true 
  495. */ 
  496. static private function valid_user() { 
  497. /** 
  498. * Determine user_id from request cookies. 
  499. * @see wp-includes/pluggable.php wp_currentuserinfo() 
  500. */ 
  501. $user_id = apply_filters( 'determine_current_user', false ); 
  502.  
  503. if ( ! $user_id ) { return false; } 
  504. if ( ! is_admin() ) { return false; } 
  505. if ( defined( 'DOING_AJAX' ) && DOING_AJAX ) { return false; } 
  506. if ( defined( 'DOING_CRON' ) && DOING_CRON ) { return false; } 
  507. if ( ! current_user_can( 'manage_options' ) ) { return false; } 
  508.  
  509. return true; 
  510.  
  511. /** 
  512. * Checks if valid reset-instructions are present. If yes, then whipe the 
  513. * plugin settings. 
  514. * 
  515. * @since 1.0.0 
  516. */ 
  517. static public function maybe_reset() { 
  518. static $Reset_Done = false; 
  519.  
  520. if ( ! $Reset_Done ) { 
  521. $Reset_Done = true; 
  522. if ( ! self::verify_token( 'reset' ) ) { return false; } 
  523.  
  524. self::cleanup_db(); 
  525. $msg = __( 'Membership 2 successfully reset!', 'membership2' ); 
  526. lib3()->ui->admin_message( $msg ); 
  527.  
  528. wp_safe_redirect( MS_Controller_Plugin::get_admin_url( 'MENU_SLUG' ) ); 
  529. exit; 
  530.  
  531. /** 
  532. * Checks if valid restore-options are specified. If they are, the snapshot 
  533. * will be restored. 
  534. * 
  535. * @since 1.0.0 
  536. * @internal This function is intended for development/testing only! 
  537. */ 
  538. static private function maybe_restore() { 
  539. static $Restore_Done = false; 
  540.  
  541. if ( ! $Restore_Done ) { 
  542. $Restore_Done = true; 
  543. if ( empty( $_POST['restore_snapshot'] ) ) { return false; } 
  544. $snapshot = $_POST['restore_snapshot']; 
  545.  
  546. if ( ! self::verify_token( 'restore' ) ) { return false; } 
  547.  
  548. lib3()->updates->plugin( 'membership2' ); 
  549. if ( lib3()->updates->restore( $snapshot ) ) { 
  550. printf( 
  551. '<p>' . 
  552. __( 'The Membership2 Snapshot "%s" was restored!', 'membership2' ) . 
  553. '</p>',  
  554. $snapshot 
  555. ); 
  556.  
  557. printf( 
  558. '<p><b>' . 
  559. __( 'To prevent auto-updating the DB again we stop here!', 'membership2' ) . 
  560. '</b></p>' 
  561. ); 
  562.  
  563. printf( 
  564. '<p>' . 
  565. __( '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.', 'membership2' ) . 
  566. '</p>',  
  567. '<a href="' . MS_Controller_Plugin::get_admin_url( 'MENU_SLUG' ) . '">',  
  568. '</a>' 
  569. ); 
  570.  
  571. wp_die( '', 'Snapshot Restored' ); 
  572.  
  573. }; 
.