is_multisite() ? 3 : 10, // A network revokes site after site (Simon, 21 Oct). 'headers' => array( 'Authorization' => 'Bearer ' . $byfaro_key ), ) ); } wp_clear_scheduled_hook( 'byfaro_daily_maintenance' ); wp_clear_scheduled_hook( 'byfaro_weekly_scan' ); wp_clear_scheduled_hook( 'byfaro_inside_gather' ); wp_clear_scheduled_hook( 'byfaro_live_retry' ); wp_clear_scheduled_hook( 'byfaro_live_saved' ); wp_clear_scheduled_hook( 'byfaro_business_send' ); // The connection: always removed. foreach ( array( 'byfaro_api_key', 'byfaro_site_id', 'byfaro_domain', 'byfaro_key_invalid', 'byfaro_last_status', 'byfaro_connect_state', 'byfaro_connect_state_at', 'byfaro_verification_token', 'byfaro_claim_status', // A scan in progress (Byfaro_Inside_Scan). 'byfaro_inside_progress', 'byfaro_inside_lock', 'byfaro_inside_ticks', // The score moving as fixes are saved (Byfaro_Live). 'byfaro_live_pending', 'byfaro_live_last', 'byfaro_live_saved', // What byfaro last sent of the "Your business" answers: forgotten, so the // answers (kept below unless the owner removes everything) go to the new // connection by themselves (Byfaro_Business::sync). Plugin 1.16/1.17's single question. 'byfaro_business_sync', 'byfaro_sells_software', 'byfaro_sells_software_sure', // The set-up wizard: a reinstall goes through it again (Jack, 6 Oct). 'byfaro_wizard', // Set up already (byfaro_setup_site()): a reinstall sets up again, schedules too. 'byfaro_site_ready', ) as $byfaro_option ) { delete_option( $byfaro_option ); } delete_transient( 'byfaro_status' ); delete_transient( 'byfaro_schema' ); delete_transient( 'byfaro_scan_running' ); delete_transient( 'byfaro_status_down' ); delete_transient( 'byfaro_about_site' ); delete_transient( 'byfaro_pricing_md_kept' ); if ( ! get_option( 'byfaro_remove_data_on_delete' ) ) { return; } // The owner asked for everything to go. global $wpdb; $wpdb->query( 'DROP TABLE IF EXISTS ' . $wpdb->prefix . 'byfaro_ai_visits' ); // phpcs:ignore WordPress.DB.PreparedSQL, WordPress.DB.DirectDatabaseQuery -- removes the plugin's own table, only when the owner chose to remove all data. foreach ( array( 'byfaro_badge_enabled', 'byfaro_badge_theme', 'byfaro_badge_style', 'byfaro_badge_type', 'byfaro_badge_align', 'byfaro_badge_live_at', 'byfaro_badge_where', 'byfaro_scan_error', 'byfaro_badge_place', 'widget_byfaro_badge', 'byfaro_llms_enabled', 'byfaro_editor_button', 'byfaro_schema_enabled', 'byfaro_blocked_bots', // Applied fixes and their history. 'byfaro_fix_ai_crawlers', 'byfaro_fix_sitemap', 'byfaro_fix_llms_txt', 'byfaro_fix_content_signal', 'byfaro_fix_org_schema', 'byfaro_fix_canonical', 'byfaro_fix_og_tags', 'byfaro_fix_org_image', 'byfaro_fix_markdown', 'byfaro_fix_link_pages', 'byfaro_fix_pricing_md', 'byfaro_fix_okf', 'byfaro_change_history', // The "Your business" answers: kept otherwise, so a reinstall doesn't ask // them again or put their checks back in the score (Jack, 20 Oct). 'byfaro_business', 'byfaro_not_applicable', 'byfaro_notice_dismissed', 'byfaro_welcome_notice', 'byfaro_remove_data_on_delete', ) as $byfaro_option ) { delete_option( $byfaro_option ); } // And anything else byfaro keeps under its own prefix (points per fix, the alt-text log, // drafts, cached page readings), so "remove all its data" leaves nothing behind // (V1 mock test, 21 Oct: byfaro_fix_points and the byfaro_findings_* transients stayed). // Only byfaro's own byfaro_ names; the owner's content (picture descriptions, pages) stays. // One by one through WordPress, so a site's object cache stays right without emptying it // (Simon, 21 Oct: a shared Redis pool would lose every site's cache). $byfaro_names = $wpdb->get_col( $wpdb->prepare( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s", $wpdb->esc_like( 'byfaro_' ) . '%', $wpdb->esc_like( '_transient_byfaro_' ) . '%' ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- finds the plugin's own options, only when the owner chose to remove all data. foreach ( (array) $byfaro_names as $byfaro_name ) { if ( 0 === strpos( $byfaro_name, '_transient_' ) ) { delete_transient( substr( $byfaro_name, strlen( '_transient_' ) ) ); } else { delete_option( $byfaro_name ); } } }; // On multisite WordPress runs this once, on the main site, so each site is visited in turn: // otherwise a subsite's live key was never revoked and its data stayed (V1 mock test P2-8). // Each site keeps or removes its data by its own "remove all byfaro data" choice. if ( is_multisite() ) { global $wpdb; foreach ( get_sites( array( 'fields' => 'ids', 'number' => 0 ) ) as $byfaro_site ) { switch_to_blog( $byfaro_site ); // Only sites byfaro ran on: nothing to revoke or remove elsewhere, and a large network // would otherwise spend its time limit on them (Simon, 21 Oct). if ( $wpdb->get_var( $wpdb->prepare( "SELECT option_name FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s LIMIT 1", $wpdb->esc_like( 'byfaro_' ) . '%', $wpdb->esc_like( '_transient_byfaro_' ) . '%' ) ) ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- finds the plugin's own options on each site, only when it is deleted. $byfaro_uninstall_site(); } restore_current_blog(); } } else { $byfaro_uninstall_site(); } // Per-user leftovers (users are shared by the whole network, so once): the guide a user had open, and "hide the byfaro button". delete_metadata( 'user', 0, 'byfaro_active_guide', '', true ); delete_metadata( 'user', 0, 'byfaro_panel_hidden', '', true );