diff --git a/amp.php b/amp.php index 0cf2334d360..8d2526ab025 100644 --- a/amp.php +++ b/amp.php @@ -1,7 +1,7 @@ Uninstall Note: To control whether all data from this plugin is deleted at uninstallation, first activate the plugin, go to the Other section on the Settings screen, and set the “Delete plugin data at uninstall” toggle. * Plugin URI: https://amp-wp.org * Author: AMP Project Contributors * Author URI: https://github.com/ampproject/amp-wp/graphs/contributors diff --git a/assets/src/settings-page/delete-data-at-uninstall.js b/assets/src/settings-page/delete-data-at-uninstall.js new file mode 100644 index 00000000000..bc93066ae3c --- /dev/null +++ b/assets/src/settings-page/delete-data-at-uninstall.js @@ -0,0 +1,39 @@ +/** + * WordPress dependencies + */ +import { useContext } from '@wordpress/element'; +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import { AMPSettingToggle } from '../components/amp-setting-toggle'; +import { Options } from '../components/options-context-provider'; +import { Loading } from '../components/loading'; + +/** + * Data deletion at uninstallation toggle on the settings page. + */ +export function DeleteDataAtUninstall() { + const { editedOptions, fetchingOptions, updateOptions } = useContext( Options ); + + if ( fetchingOptions ) { + return ; + } + + const deleteDataAtUninstall = editedOptions?.delete_data_at_uninstall; + return ( +
+ { + updateOptions( { delete_data_at_uninstall: ! deleteDataAtUninstall } ); + } } + /> +

+ { __( 'When you uninstall the plugin you have the choice of whether its data should also be deleted. Examples of plugin data include the settings, validated URLs, and transients used to store image dimensions and parsed stylesheets.', 'amp' ) } +

+
+ ); +} diff --git a/assets/src/settings-page/index.js b/assets/src/settings-page/index.js index 15fc3eb2e3c..0e5026932db 100644 --- a/assets/src/settings-page/index.js +++ b/assets/src/settings-page/index.js @@ -48,6 +48,7 @@ import { Analytics } from './analytics'; import { PairedUrlStructure } from './paired-url-structure'; import { MobileRedirection } from './mobile-redirection'; import { DeveloperTools } from './developer-tools'; +import { DeleteDataAtUninstall } from './delete-data-at-uninstall'; const { ajaxurl: wpAjaxUrl } = global; @@ -250,6 +251,7 @@ function Root( { appRoot } ) { > + diff --git a/assets/src/settings-page/style.css b/assets/src/settings-page/style.css index 3a210dbc82b..85ba6f6a436 100644 --- a/assets/src/settings-page/style.css +++ b/assets/src/settings-page/style.css @@ -610,10 +610,6 @@ li.error-kept { margin: 0; } -.amp-other-settings .developer-tools h4 { - margin-bottom: 0; -} - -.amp-other-settings .developer-tools p { +.amp-other-settings p { font-size: 0.875rem; } diff --git a/includes/amp-helper-functions.php b/includes/amp-helper-functions.php index 8b55fc2189a..ceedde3dbf8 100644 --- a/includes/amp-helper-functions.php +++ b/includes/amp-helper-functions.php @@ -98,6 +98,9 @@ function amp_bootstrap_plugin() { add_action( 'after_setup_theme', 'amp_after_setup_theme', 5 ); add_action( 'plugins_loaded', '_amp_bootstrap_customizer', 9 ); // Should be hooked before priority 10 on 'plugins_loaded' to properly unhook core panels. + + // @todo Eliminate this once https://core.trac.wordpress.org/ticket/20578 has finally landed. + add_filter( 'all_plugins', 'amp_modify_plugin_description' ); } /** @@ -219,6 +222,28 @@ function() { ); } +/** + * When AMP plugin is active remove instruction of plugin data removal steps. + * + * @since 2.2 + * @internal + * + * @param array $meta An array of plugins to display in the list table. + * @return array An array of plugins to display in the list table. + */ +function amp_modify_plugin_description( $meta ) { + + if ( isset( $meta['amp/amp.php']['Description'] ) ) { + $meta['amp/amp.php']['Description'] = preg_replace( + ':\s*.+?:', + '', + $meta['amp/amp.php']['Description'] + ); + } + + return $meta; +} + /** * Set up AMP. * diff --git a/includes/options/class-amp-options-manager.php b/includes/options/class-amp-options-manager.php index cfb4b9d36b9..97dcf48ede0 100644 --- a/includes/options/class-amp-options-manager.php +++ b/includes/options/class-amp-options-manager.php @@ -28,15 +28,16 @@ class AMP_Options_Manager { * @var array */ protected static $defaults = [ - Option::THEME_SUPPORT => AMP_Theme_Support::READER_MODE_SLUG, - Option::SUPPORTED_POST_TYPES => [ 'post', 'page' ], - Option::ANALYTICS => [], - Option::ALL_TEMPLATES_SUPPORTED => true, - Option::SUPPORTED_TEMPLATES => [ 'is_singular' ], - Option::VERSION => AMP__VERSION, - Option::READER_THEME => ReaderThemes::DEFAULT_READER_THEME, - Option::PAIRED_URL_STRUCTURE => Option::PAIRED_URL_STRUCTURE_QUERY_VAR, - Option::PLUGIN_CONFIGURED => false, + Option::THEME_SUPPORT => AMP_Theme_Support::READER_MODE_SLUG, + Option::SUPPORTED_POST_TYPES => [ 'post', 'page' ], + Option::ANALYTICS => [], + Option::ALL_TEMPLATES_SUPPORTED => true, + Option::SUPPORTED_TEMPLATES => [ 'is_singular' ], + Option::VERSION => AMP__VERSION, + Option::READER_THEME => ReaderThemes::DEFAULT_READER_THEME, + Option::PAIRED_URL_STRUCTURE => Option::PAIRED_URL_STRUCTURE_QUERY_VAR, + Option::PLUGIN_CONFIGURED => false, + Option::DELETE_DATA_AT_UNINSTALL => true, ]; /** @@ -320,6 +321,10 @@ public static function validate_options( $new_options ) { $options[ Option::PLUGIN_CONFIGURED ] = (bool) $new_options[ OPTION::PLUGIN_CONFIGURED ]; } + if ( isset( $new_options[ Option::DELETE_DATA_AT_UNINSTALL ] ) ) { + $options[ Option::DELETE_DATA_AT_UNINSTALL ] = (bool) $new_options[ OPTION::DELETE_DATA_AT_UNINSTALL ]; + } + // Validate analytics. if ( isset( $new_options[ Option::ANALYTICS ] ) && $new_options[ Option::ANALYTICS ] !== $options[ Option::ANALYTICS ] ) { $new_analytics_option = []; diff --git a/includes/uninstall-functions.php b/includes/uninstall-functions.php index 2b8ff94ca6a..8ce3abd39c1 100644 --- a/includes/uninstall-functions.php +++ b/includes/uninstall-functions.php @@ -175,7 +175,7 @@ function delete_transients() { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Cannot cache result since we're deleting the records. $wpdb->query( - // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- See use of prepare in foreach loop above. + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- See use of prepare in foreach loop above. "DELETE FROM $wpdb->options WHERE " . implode( ' OR ', $where_clause ) ); } @@ -187,10 +187,17 @@ function delete_transients() { * @internal */ function remove_plugin_data() { + $options = get_option( 'amp-options' ); - delete_options(); - delete_user_metadata(); - delete_posts(); - delete_terms(); - delete_transients(); + if ( + is_array( $options ) && array_key_exists( 'delete_data_at_uninstall', $options ) + ? $options['delete_data_at_uninstall'] + : true + ) { + delete_options(); + delete_user_metadata(); + delete_posts(); + delete_terms(); + delete_transients(); + } } diff --git a/src/Option.php b/src/Option.php index a8371fcfcf1..4aee94fb06a 100644 --- a/src/Option.php +++ b/src/Option.php @@ -159,6 +159,13 @@ interface Option { */ const PLUGIN_CONFIGURED = 'plugin_configured'; + /** + * The key of the option storing whether to delete AMP data upon uninstalling the plugin. + * + * @var string + */ + const DELETE_DATA_AT_UNINSTALL = 'delete_data_at_uninstall'; + /** * Cached slug when it is defined late. * diff --git a/src/OptionsRESTController.php b/src/OptionsRESTController.php index 57a6fd433bc..07d6b996da5 100644 --- a/src/OptionsRESTController.php +++ b/src/OptionsRESTController.php @@ -280,7 +280,7 @@ public function get_item_schema() { 'type' => 'object', 'properties' => [ // Note: The sanitize_callback from AMP_Options_Manager::register_settings() is applying to this option. - Option::THEME_SUPPORT => [ + Option::THEME_SUPPORT => [ 'type' => 'string', 'enum' => [ AMP_Theme_Support::READER_MODE_SLUG, @@ -288,7 +288,7 @@ public function get_item_schema() { AMP_Theme_Support::TRANSITIONAL_MODE_SLUG, ], ], - Option::READER_THEME => [ + Option::READER_THEME => [ 'type' => 'string', 'arg_options' => [ 'validate_callback' => function ( $value ) { @@ -297,57 +297,61 @@ public function get_item_schema() { }, ], ], - Option::MOBILE_REDIRECT => [ + Option::MOBILE_REDIRECT => [ 'type' => 'boolean', 'default' => false, ], - self::PREVIEW_PERMALINK => [ + self::PREVIEW_PERMALINK => [ 'type' => 'string', 'readonly' => true, 'format' => 'url', ], - Option::PLUGIN_CONFIGURED => [ + Option::PLUGIN_CONFIGURED => [ 'type' => 'boolean', 'default' => false, ], - Option::ALL_TEMPLATES_SUPPORTED => [ + Option::ALL_TEMPLATES_SUPPORTED => [ 'type' => 'boolean', ], - Option::SUPPRESSED_PLUGINS => [ + Option::SUPPRESSED_PLUGINS => [ 'type' => 'object', ], - self::SUPPRESSIBLE_PLUGINS => [ + self::SUPPRESSIBLE_PLUGINS => [ 'type' => 'object', 'readonly' => true, ], - Option::SUPPORTED_TEMPLATES => [ + Option::SUPPORTED_TEMPLATES => [ 'type' => 'array', 'items' => [ 'type' => 'string', ], ], - Option::SUPPORTED_POST_TYPES => [ + Option::SUPPORTED_POST_TYPES => [ 'type' => 'array', 'items' => [ 'type' => 'string', ], ], - Option::ANALYTICS => [ + Option::ANALYTICS => [ 'type' => 'object', ], - self::SUPPORTABLE_POST_TYPES => [ + Option::DELETE_DATA_AT_UNINSTALL => [ + 'type' => 'boolean', + 'default' => true, + ], + self::SUPPORTABLE_POST_TYPES => [ 'type' => 'array', 'readonly' => true, ], - self::SUPPORTABLE_TEMPLATES => [ + self::SUPPORTABLE_TEMPLATES => [ 'type' => 'array', 'readonly' => true, ], - self::ONBOARDING_WIZARD_LINK => [ + self::ONBOARDING_WIZARD_LINK => [ 'type' => 'url', 'readonly' => true, ], - self::CUSTOMIZER_LINK => [ + self::CUSTOMIZER_LINK => [ 'type' => 'url', 'readonly' => true, ], diff --git a/tests/php/src/OptionsRESTControllerTest.php b/tests/php/src/OptionsRESTControllerTest.php index f40738488d6..6dffe33f236 100644 --- a/tests/php/src/OptionsRESTControllerTest.php +++ b/tests/php/src/OptionsRESTControllerTest.php @@ -71,6 +71,7 @@ public function test_get_items() { 'mobile_redirect', 'plugin_configured', 'all_templates_supported', + 'delete_data_at_uninstall', 'suppressed_plugins', 'supported_templates', 'supported_post_types', diff --git a/tests/php/test-amp-helper-functions.php b/tests/php/test-amp-helper-functions.php index a834d918955..f1b0c5943e5 100644 --- a/tests/php/test-amp-helper-functions.php +++ b/tests/php/test-amp-helper-functions.php @@ -127,6 +127,7 @@ public function test_amp_bootstrap_plugin() { $this->assertEquals( PHP_INT_MAX, has_filter( 'script_loader_tag', 'amp_filter_script_loader_tag' ) ); $this->assertEquals( 10, has_filter( 'style_loader_tag', 'amp_filter_font_style_loader_tag_with_crossorigin_anonymous' ) ); + $this->assertEquals( 10, has_filter( 'all_plugins', 'amp_modify_plugin_description' ) ); } /** @covers ::amp_bootstrap_plugin() */ @@ -2417,6 +2418,26 @@ public function test_amp_has_paired_endpoint_passed( $paired_url_structure, $suf $this->assertEquals( $is_amp, amp_has_paired_endpoint( $url ) ); } + /** + * @covers ::amp_modify_plugin_description() + */ + public function test_amp_modify_plugin_description() { + $input = [ + 'amp/amp.php' => [ + 'Description' => 'An easier path to great Page Experience for everyone. Powered by AMP. Deletion Note: To delete all plugin data with uninstallation, first activate the plugin, Go to Settings screen > Scroll to “Other” section in Advanced settings > Enable “Delete plugin data upon uninstall” toggle (if you haven\'t done so already).', + ], + 'foo/foo.php' => [ + 'Description' => 'This is not about foo[d]!', + ], + ]; + + $expected = $input; + + $expected['amp/amp.php']['Description'] = 'An easier path to great Page Experience for everyone. Powered by AMP.'; + + $this->assertEquals( $expected, amp_modify_plugin_description( $input ) ); + } + /** * Get a mock publisher logo URL, to test that the filter works as expected. * diff --git a/tests/php/test-class-amp-options-manager.php b/tests/php/test-class-amp-options-manager.php index 96a52b3bb50..cb01bcd3c88 100644 --- a/tests/php/test-class-amp-options-manager.php +++ b/tests/php/test-class-amp-options-manager.php @@ -107,18 +107,19 @@ public function test_get_and_set_options() { delete_option( AMP_Options_Manager::OPTION_NAME ); $this->assertEquals( [ - Option::THEME_SUPPORT => AMP_Theme_Support::READER_MODE_SLUG, - Option::SUPPORTED_POST_TYPES => [ 'post', 'page' ], - Option::ANALYTICS => [], - Option::ALL_TEMPLATES_SUPPORTED => true, - Option::SUPPORTED_TEMPLATES => [ 'is_singular' ], - Option::SUPPRESSED_PLUGINS => [], - Option::VERSION => AMP__VERSION, - Option::MOBILE_REDIRECT => true, - Option::READER_THEME => 'legacy', - Option::PLUGIN_CONFIGURED => false, - Option::PAIRED_URL_STRUCTURE => Option::PAIRED_URL_STRUCTURE_QUERY_VAR, - Option::LATE_DEFINED_SLUG => null, + Option::THEME_SUPPORT => AMP_Theme_Support::READER_MODE_SLUG, + Option::SUPPORTED_POST_TYPES => [ 'post', 'page' ], + Option::ANALYTICS => [], + Option::ALL_TEMPLATES_SUPPORTED => true, + Option::SUPPORTED_TEMPLATES => [ 'is_singular' ], + Option::SUPPRESSED_PLUGINS => [], + Option::VERSION => AMP__VERSION, + Option::MOBILE_REDIRECT => true, + Option::READER_THEME => 'legacy', + Option::PLUGIN_CONFIGURED => false, + Option::PAIRED_URL_STRUCTURE => Option::PAIRED_URL_STRUCTURE_QUERY_VAR, + Option::LATE_DEFINED_SLUG => null, + Option::DELETE_DATA_AT_UNINSTALL => true, ], AMP_Options_Manager::get_options() ); @@ -262,21 +263,25 @@ public function test_get_options_changing_plugin_configured_default() { update_option( AMP_Options_Manager::OPTION_NAME, [ - Option::VERSION => AMP__VERSION, - Option::PLUGIN_CONFIGURED => false, + Option::VERSION => AMP__VERSION, + Option::PLUGIN_CONFIGURED => false, + Option::DELETE_DATA_AT_UNINSTALL => false, ] ); $this->assertFalse( AMP_Options_Manager::get_option( Option::PLUGIN_CONFIGURED ) ); + $this->assertFalse( AMP_Options_Manager::get_option( Option::DELETE_DATA_AT_UNINSTALL ) ); // Ensure plugin_configured is false when explicitly set as such in the DB. update_option( AMP_Options_Manager::OPTION_NAME, [ - Option::VERSION => AMP__VERSION, - Option::PLUGIN_CONFIGURED => true, + Option::VERSION => AMP__VERSION, + Option::PLUGIN_CONFIGURED => true, + Option::DELETE_DATA_AT_UNINSTALL => true, ] ); $this->assertTrue( AMP_Options_Manager::get_option( Option::PLUGIN_CONFIGURED ) ); + $this->assertTrue( AMP_Options_Manager::get_option( Option::DELETE_DATA_AT_UNINSTALL ) ); } /** @return array */ diff --git a/tests/php/test-uninstall.php b/tests/php/test-uninstall.php index e08448abe0a..86b628e0030 100644 --- a/tests/php/test-uninstall.php +++ b/tests/php/test-uninstall.php @@ -6,6 +6,7 @@ */ use AmpProject\AmpWP\Tests\TestCase; +use AmpProject\AmpWP\Option; /** * @runInSeparateProcess @@ -110,11 +111,23 @@ public function test_uninstall_php() { define( 'WP_UNINSTALL_PLUGIN', 'Yes' ); } + // Test 1: With option to keep AMP data ON. + AMP_Options_Manager::update_option( Option::DELETE_DATA_AT_UNINSTALL, false ); + + require AMP__DIR__ . '/uninstall.php'; + + $this->flush_cache(); + + $this->assertNotEmpty( get_option( AMP_Options_Manager::OPTION_NAME, false ) ); + + // Test 2: With option to keep AMP data OFF. + AMP_Options_Manager::update_option( Option::DELETE_DATA_AT_UNINSTALL, true ); + require AMP__DIR__ . '/uninstall.php'; $this->flush_cache(); - $this->assertEmpty( get_option( 'amp-option', false ) ); + $this->assertEmpty( get_option( AMP_Options_Manager::OPTION_NAME, false ) ); $this->assertEmpty( get_post( $amp_validated_post->ID ) ); $this->assertEmpty( get_term( $amp_error_term->term_id ) );