prefix plus literals; prepare() cannot parameterize identifiers); runs only from uninstall.php behind the delete_data_on_uninstall opt-in, never on a regular request. $wpdb->query( "DROP TABLE IF EXISTS {$wpdb->prefix}{$t}" ); } /* * Options. The two _v1 flags are the reason this list exists in more than two * lines: each guards a run-once migration whose SUBJECT is deleted just above, * so leaving the flag behind meant a reinstall seeded fresh defaults and then * skipped the migration that was supposed to run against them — silently, with * nothing to indicate it. Any future run-once flag belongs here on the day it * is introduced. * * Names are spelled out rather than read from their class constants because * those constants are private, and pulling four more files into the uninstall * scope to reach them would trade a typo risk for a fatal-error risk. Keep each * string in step with its declaration: * euqin_defaults_materialized_v1 Euqin_Defaults_Materialization::FLAG_OPTION * euqin_email_wc_settings_migrated_v1 Euqin_Email_Settings_Migration::FLAG_OPTION */ // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- see the note on $settings above: uninstall_plugin() function scope. $options = array( Euqin_Settings::OPTION_KEY, 'euqin_db_version', 'euqin_defaults_materialized_v1', 'euqin_email_wc_settings_migrated_v1', // Written by the email settings migration, read by the email registry. // Goes with the flag above: kept without it, a reinstall would carry the old // site's urgent prefix into a database that has nothing else left of it. 'euqin_admin_new_order_urgent_subject_prefix_stored', ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.NonPrefixedVariableFound -- see the note on $settings above: uninstall_plugin() function scope. foreach ( $options as $option ) { delete_option( $option ); } /* * A TRANSIENT, not an option — delete_option() would not touch it, because the * row is named _transient_euqin_activation_notice. It expires on its own in a * week, so this is tidiness rather than correctness. * * The two per-user transients are deliberately NOT removed: euqin_tools_flash_* * and euqin_mg_save_conflicts_* are suffixed with a user ID, so clearing them * would mean a LIKE scan of wp_options to catch keys this file cannot enumerate. * Both are admin-only flash messages measured in seconds. */ delete_transient( 'euqin_activation_notice' ); /* * User meta, so neither delete_option() nor delete_transient() reaches it: the * dashboard stores each admin's dismissed-readiness signature per user. * * The argument shape is load-bearing and easy to get subtly wrong. The final * true is $delete_all, which drops the WHERE clause on the user ID and so * clears the key for EVERY user; without it this would only ever affect one * account. The 0 is $object_id, ignored while $delete_all is set — core's * guards accept it precisely because $delete_all is true. The empty $meta_value * means "any value", which is what we want: signatures differ per user and per * checklist state, so matching on one would leave most rows behind. * * Nothing breaks when the key is absent — that is the normal state for a new * user, and the dashboard compares get_user_meta()'s '' against a signature * hash, which simply shows the checklist again. */ delete_metadata( 'user', 0, 'euqin_readiness_dismissed', '', true );