| @@ -1,19 +1,25 @@ | ||
| 1 | 1 | <?php |
| 2 | 2 | |
| 3 | 3 | /** |
| 4 | - * Fired when the plugin is uninstalled (deleted from the Plugins screen). | |
| 4 | + * Fired when the plugin is uninstalled. | |
| 5 | 5 | * |
| 6 | - * This is the plugin's single place for permanent data removal — the | |
| 7 | - * deactivator intentionally preserves the account connection (issue #112), | |
| 8 | - * so credentials/tokens are cleaned up ONLY here. WordPress runs this file | |
| 9 | - * on its own, without loading the plugin, so only core WP functions may be | |
| 10 | - * used below. | |
| 6 | + * When populating this file, consider the following flow | |
| 7 | + * of control: | |
| 11 | 8 | * |
| 12 | - * Scope: plugin-owned options, cached data, user meta, scheduled events and | |
| 13 | - * generated Streamify files are removed. WpStream content is preserved unless | |
| 14 | - * the site explicitly opts in to deleting it. | |
| 9 | + * - This method should be static | |
| 10 | + * - Check if the $_REQUEST content actually is the plugin name | |
| 11 | + * - Run an admin referrer check to make sure it goes through authentication | |
| 12 | + * - Verify the output of $_GET makes sense | |
| 13 | + * - Repeat with other user roles. Best directly by using the links/query string parameters. | |
| 14 | + * - Repeat things for multisite. Once for a single site in the network, once sitewide. | |
| 15 | 15 | * |
| 16 | + * This file may be updated more in future version of the Boilerplate; however, this is the | |
| 17 | + * general skeleton and outline for how the file should work. | |
| 18 | + * | |
| 19 | + * For more information, see the following discussion: | |
| 20 | + * https://github.com/tommcfarlin/WordPress-Plugin-Boilerplate/pull/123#issuecomment-28541913 | |
| 21 | + * | |
| 16 | 22 | * @link http://wpstream.net |
| 17 | 23 | * @since 3.0.1 |
| 18 | 24 | * |
| 19 | 25 | * @package Wpstream |
| @@ -19,151 +25,7 @@ | ||
| 19 | 25 | * @package Wpstream |
| 20 | 26 | */ |
| 21 | 27 | |
| 22 | 28 | // If uninstall not called from WordPress, then exit. |
| 23 | -// WP_UNINSTALL_PLUGIN is only defined when WordPress runs this file through the | |
| 24 | -// official uninstall routine; its absence means the file was reached directly | |
| 25 | -// (e.g. a direct HTTP request), so we abort to prevent arbitrary execution. | |
| 26 | 29 | if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) { |
| 27 | - // Not a legitimate uninstall context — stop immediately. | |
| 28 | 30 | exit; |
| 29 | -} | |
| 30 | - | |
| 31 | -global $wpdb; | |
| 32 | - | |
| 33 | -/** | |
| 34 | - * Remove all WpStream-owned data from the current site's tables. | |
| 35 | - * | |
| 36 | - * The current-site seam is intentional: switch_to_blog() changes $wpdb's site | |
| 37 | - * tables, allowing the same cleanup implementation to serve single-site and | |
| 38 | - * multisite installations without separate inventories. | |
| 39 | - */ | |
| 40 | -$wpstream_cleanup_current_site = static function () use ( $wpdb ) { | |
| 41 | - // Read the destructive preference before the wpstream_* option sweep. | |
| 42 | - $wpstream_delete_content_option = get_option( 'wpstream_delete_content_on_uninstall', 'no' ); | |
| 43 | - $wpstream_delete_content = in_array( | |
| 44 | - $wpstream_delete_content_option, | |
| 45 | - array( true, 1, '1', 'yes' ), | |
| 46 | - true | |
| 47 | - ); | |
| 48 | - | |
| 49 | - // Credentials and tokens are explicitly removed even if a later bulk query | |
| 50 | - // fails, because plaintext secrets must never outlive the plugin. | |
| 51 | - delete_option( 'wpstream_api_key' ); | |
| 52 | - delete_option( 'wpstream_api_secret_key' ); | |
| 53 | - delete_option( 'wpstream_api_username' ); | |
| 54 | - delete_option( 'wpstream_api_password' ); | |
| 55 | - delete_option( 'wpstream_current_token' ); | |
| 56 | - delete_option( 'wpstream_token_expire' ); | |
| 57 | - // Clean residual pre-TASK-31 names if migration never had a chance to run. | |
| 58 | - delete_option( 'wp_estate_curent_token' ); | |
| 59 | - delete_option( 'wp_estate_token_expire' ); | |
| 60 | - delete_transient( 'wpstream_token_api' ); | |
| 61 | - delete_transient( 'wpstream_token_request_30' ); | |
| 62 | - | |
| 63 | - wp_clear_scheduled_hook( 'wpstream_log_cleanup' ); | |
| 64 | - wp_clear_scheduled_hook( 'wpstreamify_cleanup_event' ); | |
| 65 | - | |
| 66 | - // Remove dynamic plugin options that cannot be enumerated safely. Escaping | |
| 67 | - // underscores prevents SQL LIKE from broadening the ownership prefixes. | |
| 68 | - $wpstream_option_patterns = array( | |
| 69 | - $wpdb->esc_like( 'wpstream_' ) . '%', | |
| 70 | - $wpdb->esc_like( 'wp_estate_' ) . '%', | |
| 71 | - $wpdb->esc_like( 'wp_stream_' ) . '%', | |
| 72 | - $wpdb->esc_like( '_transient_free_event_streamName_' ) . '%', | |
| 73 | - $wpdb->esc_like( '_transient_timeout_free_event_streamName_' ) . '%', | |
| 74 | - $wpdb->esc_like( '_transient_paid_event_streamName_' ) . '%', | |
| 75 | - $wpdb->esc_like( '_transient_timeout_paid_event_streamName_' ) . '%', | |
| 76 | - $wpdb->esc_like( '_transient_wpstream_' ) . '%', | |
| 77 | - $wpdb->esc_like( '_transient_timeout_wpstream_' ) . '%', | |
| 78 | - $wpdb->esc_like( '_transient_' ) . '%' . $wpdb->esc_like( '_api20_streamName' ), | |
| 79 | - $wpdb->esc_like( '_transient_timeout_' ) . '%' . $wpdb->esc_like( '_api20_streamName' ), | |
| 80 | - $wpdb->esc_like( '_transient_vod_decryption_key_index_' ) . '%', | |
| 81 | - $wpdb->esc_like( '_transient_timeout_vod_decryption_key_index_' ) . '%', | |
| 82 | - $wpdb->esc_like( '_transient_paid_vod_key_index_' ) . '%', | |
| 83 | - $wpdb->esc_like( '_transient_timeout_paid_vod_key_index_' ) . '%', | |
| 84 | - $wpdb->esc_like( '_transient_event_data_to_return_' ) . '%', | |
| 85 | - $wpdb->esc_like( '_transient_timeout_event_data_to_return_' ) . '%', | |
| 86 | - $wpdb->esc_like( '_transient_server_id_to_return_' ) . '%', | |
| 87 | - $wpdb->esc_like( '_transient_timeout_server_id_to_return_' ) . '%', | |
| 88 | - ); | |
| 89 | - $wpstream_option_conditions = implode( | |
| 90 | - ' OR ', | |
| 91 | - array_fill( 0, count( $wpstream_option_patterns ), 'option_name LIKE %s' ) | |
| 92 | - ); | |
| 93 | - $wpdb->query( | |
| 94 | - $wpdb->prepare( | |
| 95 | - "DELETE FROM {$wpdb->options} WHERE {$wpstream_option_conditions}", | |
| 96 | - $wpstream_option_patterns | |
| 97 | - ) | |
| 98 | - ); | |
| 99 | - | |
| 100 | - // User-specific state is normally prefixed; the BuddyBoss integration also | |
| 101 | - // shipped one legacy misspelled key which remains plugin-owned. | |
| 102 | - $wpstream_usermeta_patterns = array( | |
| 103 | - $wpdb->esc_like( 'wpstream_' ) . '%', | |
| 104 | - $wpdb->esc_like( 'budyboss_selected_channel' ), | |
| 105 | - ); | |
| 106 | - $wpdb->query( | |
| 107 | - $wpdb->prepare( | |
| 108 | - "DELETE FROM {$wpdb->usermeta} WHERE meta_key LIKE %s OR meta_key LIKE %s", | |
| 109 | - $wpstream_usermeta_patterns | |
| 110 | - ) | |
| 111 | - ); | |
| 112 | - | |
| 113 | - // Authored content is valuable, so delete it only for this site's opt-in. | |
| 114 | - if ( $wpstream_delete_content ) { | |
| 115 | - $wpstream_content_ids = get_posts( | |
| 116 | - array( | |
| 117 | - 'post_type' => array( 'wpstream_product', 'wpstream_product_vod', 'wpstream_bundles' ), | |
| 118 | - 'post_status' => 'any', | |
| 119 | - 'numberposts' => -1, | |
| 120 | - 'fields' => 'ids', | |
| 121 | - 'suppress_filters' => true, | |
| 122 | - ) | |
| 123 | - ); | |
| 124 | - | |
| 125 | - foreach ( $wpstream_content_ids as $wpstream_content_id ) { | |
| 126 | - wp_delete_post( (int) $wpstream_content_id, true ); | |
| 127 | - } | |
| 128 | - } | |
| 129 | -}; | |
| 130 | - | |
| 131 | -if ( is_multisite() ) { | |
| 132 | - $wpstream_site_ids = get_sites( | |
| 133 | - array( | |
| 134 | - 'fields' => 'ids', | |
| 135 | - 'number' => 0, | |
| 136 | - ) | |
| 137 | - ); | |
| 138 | - | |
| 139 | - foreach ( $wpstream_site_ids as $wpstream_site_id ) { | |
| 140 | - switch_to_blog( (int) $wpstream_site_id ); | |
| 141 | - try { | |
| 142 | - $wpstream_cleanup_current_site(); | |
| 143 | - } finally { | |
| 144 | - restore_current_blog(); | |
| 145 | - } | |
| 146 | - } | |
| 147 | -} else { | |
| 148 | - $wpstream_cleanup_current_site(); | |
| 149 | -} | |
| 150 | - | |
| 151 | -// Streamify creates only generated cache data here, so the complete directory | |
| 152 | -// may be removed. Files are traversed child-first to support nested cache paths. | |
| 153 | -$wpstream_cache_dir = WP_CONTENT_DIR . '/uploads/wpstreamify_cache'; | |
| 154 | -if ( is_dir( $wpstream_cache_dir ) ) { | |
| 155 | - $wpstream_cache_items = new RecursiveIteratorIterator( | |
| 156 | - new RecursiveDirectoryIterator( $wpstream_cache_dir, FilesystemIterator::SKIP_DOTS ), | |
| 157 | - RecursiveIteratorIterator::CHILD_FIRST | |
| 158 | - ); | |
| 159 | - | |
| 160 | - foreach ( $wpstream_cache_items as $wpstream_cache_item ) { | |
| 161 | - if ( $wpstream_cache_item->isDir() && ! $wpstream_cache_item->isLink() ) { | |
| 162 | - rmdir( $wpstream_cache_item->getPathname() ); | |
| 163 | - } else { | |
| 164 | - unlink( $wpstream_cache_item->getPathname() ); | |
| 165 | - } | |
| 166 | - } | |
| 167 | - | |
| 168 | - rmdir( $wpstream_cache_dir ); | |
| 169 | 31 | } |