> $plugins get_plugins() output. * @return string */ function ai_o_optimiser_pro_addon_basename( array $plugins ) { $self = defined( 'WP_UNINSTALL_PLUGIN' ) ? (string) WP_UNINSTALL_PLUGIN : ''; foreach ( $plugins as $basename => $data ) { if ( $basename === $self || ! is_array( $data ) ) { continue; } $requires = isset( $data['RequiresPlugins'] ) ? (string) $data['RequiresPlugins'] : ''; if ( '' !== $requires && in_array( 'ai-o-optimiser', array_map( 'trim', explode( ',', $requires ) ), true ) ) { return (string) $basename; } $suffix = '/ai-o-optimiser-pro.php'; if ( $suffix === substr( (string) $basename, -strlen( $suffix ) ) ) { return (string) $basename; } } return ''; } endif; if ( ! function_exists( 'ai_o_optimiser_sibling_edition_installed' ) ) : function ai_o_optimiser_sibling_edition_installed() { if ( ! function_exists( 'get_plugins' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } if ( ! function_exists( 'get_plugins' ) ) { // Without the plugin API there is no safe way to tell. Assume a // sibling and delete nothing: leaving data behind is recoverable, // destroying a live install's history is not. return true; } $self = defined( 'WP_UNINSTALL_PLUGIN' ) ? (string) WP_UNINSTALL_PLUGIN : ''; $domains = array( 'ai-o-optimiser', 'ai-crawler-tracker' ); $plugins = get_plugins(); $addon = ai_o_optimiser_pro_addon_basename( $plugins ); foreach ( $plugins as $ai_o_optimiser_basename => $ai_o_optimiser_data ) { if ( $ai_o_optimiser_basename === $self || $ai_o_optimiser_basename === $addon ) { continue; } $ai_o_optimiser_domain = isset( $ai_o_optimiser_data['TextDomain'] ) ? (string) $ai_o_optimiser_data['TextDomain'] : ''; if ( in_array( $ai_o_optimiser_domain, $domains, true ) ) { return true; } } return false; } endif; if ( ai_o_optimiser_sibling_edition_installed() ) { return; } $ai_o_optimiser_resources_file = __DIR__ . '/includes/ai-o-optimiser-owned-resources.php'; $ai_o_optimiser_resources = file_exists( $ai_o_optimiser_resources_file ) ? require $ai_o_optimiser_resources_file : array(); /* * While the delta Pro add-on is installed, the Pro-exclusive subsets of the * registry are its to remove, through its own uninstall.php, and are taken * out of every list below. Otherwise -- Free alone, the standalone paid * package alone, or a Free that once hosted the standalone -- this handler * cleans up everything it always has, so nothing is left behind by a package * that no longer exists to do it. */ if ( ! function_exists( 'ai_o_optimiser_uninstall_owned' ) ) : /** * A registry list minus the add-on's share of it, while the add-on is installed. * * @param mixed $all The union list from the registry. * @param mixed $pro The Pro-exclusive subset of it. * @param bool $addon_present Whether the delta Pro add-on is installed. * @return string[] */ function ai_o_optimiser_uninstall_owned( $all, $pro, $addon_present ) { $all = is_array( $all ) ? $all : array(); $pro = is_array( $pro ) ? $pro : array(); return $addon_present ? array_values( array_diff( $all, $pro ) ) : $all; } endif; $ai_o_optimiser_addon_present = function_exists( 'get_plugins' ) && '' !== ai_o_optimiser_pro_addon_basename( get_plugins() ); $ai_o_optimiser_cron_hooks = ai_o_optimiser_uninstall_owned( $ai_o_optimiser_resources['cron_hooks'] ?? array(), $ai_o_optimiser_resources['pro_cron_hooks'] ?? array(), $ai_o_optimiser_addon_present ); // Stop all plugin-owned work regardless of the data-retention choice. // wp_unschedule_hook(), not wp_clear_scheduled_hook(): the Local events are // always scheduled with a job/location arg, which the latter's implicit // empty args never match, so they would survive deletion. foreach ( $ai_o_optimiser_cron_hooks as $ai_o_optimiser_hook ) { wp_unschedule_hook( (string) $ai_o_optimiser_hook ); } $ai_o_optimiser_administrator = get_role( 'administrator' ); if ( $ai_o_optimiser_administrator ) { $ai_o_optimiser_administrator->remove_cap( 'manage_ai_o_optimiser_sensitive' ); } $ai_o_optimiser_settings = get_option( 'ai_o_optimiser_settings', array() ); $ai_o_optimiser_should_delete = ! empty( $ai_o_optimiser_settings['delete_data_on_uninstall'] ); if ( ! $ai_o_optimiser_should_delete ) { return; } // Drop every plugin-owned/legacy-compatible table from the canonical registry. $ai_o_optimiser_tables = isset( $ai_o_optimiser_resources['tables'] ) && is_array( $ai_o_optimiser_resources['tables'] ) ? $ai_o_optimiser_resources['tables'] : array(); foreach ( $ai_o_optimiser_tables as $ai_o_optimiser_table_suffix ) { $ai_o_optimiser_table_suffix = preg_replace( '/[^A-Za-z0-9_]/', '', (string) $ai_o_optimiser_table_suffix ); if ( '' === $ai_o_optimiser_table_suffix ) { continue; } $ai_o_optimiser_table_name = $wpdb->prefix . $ai_o_optimiser_table_suffix; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.DirectDatabaseQuery.SchemaChange -- One-time uninstall cleanup of plugin-owned data; caching is not applicable. $wpdb->query( $wpdb->prepare( 'DROP TABLE IF EXISTS %i', $ai_o_optimiser_table_name ) ); } // Remove every plugin-owned persistent option from the same registry. $ai_o_optimiser_options = ai_o_optimiser_uninstall_owned( $ai_o_optimiser_resources['options'] ?? array(), $ai_o_optimiser_resources['pro_options'] ?? array(), $ai_o_optimiser_addon_present ); foreach ( $ai_o_optimiser_options as $ai_o_optimiser_option ) { $ai_o_optimiser_option = (string) $ai_o_optimiser_option; delete_option( $ai_o_optimiser_option ); delete_site_option( $ai_o_optimiser_option ); // No-op on single-site; covers network-level compatible state. } // Post meta written by Schema/Search Ops workflows, from the same registry. $ai_o_optimiser_meta_keys = ai_o_optimiser_uninstall_owned( $ai_o_optimiser_resources['post_meta'] ?? array(), $ai_o_optimiser_resources['pro_post_meta'] ?? array(), $ai_o_optimiser_addon_present ); foreach ( $ai_o_optimiser_meta_keys as $ai_o_optimiser_meta_key ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.SlowDBQuery.slow_db_query_meta_key -- One-time uninstall cleanup of plugin-owned data; caching and meta-key indexing are not applicable. $wpdb->delete( $wpdb->postmeta, array( 'meta_key' => $ai_o_optimiser_meta_key ) ); } // Remove plugin transients/locks left by interrupted background/admin work. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching -- One-time uninstall cleanup of plugin-owned data; caching is not applicable. $wpdb->query( $wpdb->prepare( "DELETE FROM %i WHERE option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s OR option_name LIKE %s", $wpdb->options, $wpdb->esc_like( '_transient_ai_o_optimiser_' ) . '%', $wpdb->esc_like( '_transient_timeout_ai_o_optimiser_' ) . '%', $wpdb->esc_like( '_transient_ai_o_optimiser_metadata_' ) . '%', $wpdb->esc_like( '_transient_timeout_ai_o_optimiser_metadata_' ) . '%', $wpdb->esc_like( 'ai_o_optimiser_schema_scan_rows_' ) . '%', $wpdb->esc_like( 'ai_o_optimiser_runtime_lock_' ) . '%' ) );