- Privileged accounts: the UPN rule also applies when bind_oid is off or the account is not bound; privileges are checked on every site of a multisite user, include code/HTML capabilities (unfiltered_html, plugins, themes, users) and the remembered roles of deactivated accounts. - send_auth_cookies protection also works on WordPress 6.0/6.1. - Run lock via INSERT IGNORE (atomic), refreshed during long runs; a shutdown handler reports fatal errors and frees the lock. - Deprovisioning only for accounts linked in the current tenant (tenant recorded per account; legacy links not found are left alone). - Safety stop based on the accounts linked before the run; new safety stop for removals of administrative roles. - Disable is idempotent; row-action nonces are bound to the state. - Profile photos are re-encoded to 240 px (drops EXIF and appended data), size-limited while downloading, removed on deactivation; index.php guard in the photo folder. - Privacy exporter and eraser for the copied data. - One-time migration hardens accounts deactivated by 1.0 and cleans a stored certificate bundle; the .cer download is always re-exported. - Password fields hidden in button-only mode even when the connection is broken; settings written non-autoloaded; robust user ID queries. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
69 lines
2.4 KiB
PHP
69 lines
2.4 KiB
PHP
<?php
|
|
/**
|
|
* Removes all plugin data on uninstall.
|
|
*
|
|
* @package M365_Login
|
|
*/
|
|
|
|
if ( ! defined( 'WP_UNINSTALL_PLUGIN' ) ) {
|
|
exit;
|
|
}
|
|
|
|
global $wpdb;
|
|
|
|
/**
|
|
* Deletes options, transients and user meta for one site.
|
|
*/
|
|
function m365_login_uninstall_site() {
|
|
global $wpdb;
|
|
|
|
// Cached Graph app token (may live in a persistent object cache instead of the options table).
|
|
$settings = get_option( 'm365_login_settings', array() );
|
|
if ( is_array( $settings ) && ! empty( $settings['client_id'] ) ) {
|
|
$tenant = ! empty( $settings['tenant_id'] ) ? $settings['tenant_id'] : 'organizations';
|
|
foreach ( array( 'secret', 'certificate' ) as $method ) {
|
|
delete_transient( 'm365_login_apptoken_' . md5( $tenant . '|' . $settings['client_id'] . '|' . $method ) );
|
|
}
|
|
}
|
|
|
|
delete_option( 'm365_login_settings' );
|
|
delete_option( 'm365_login_sync_lock' );
|
|
delete_option( 'm365_login_version' );
|
|
delete_option( 'm365_login_sync_report' );
|
|
wp_clear_scheduled_hook( 'm365_login_sync' );
|
|
|
|
// Synced profile photos (uploads/m365-login-avatars/).
|
|
$uploads = wp_get_upload_dir();
|
|
$dir = trailingslashit( $uploads['basedir'] ) . 'm365-login-avatars';
|
|
if ( is_dir( $dir ) ) {
|
|
foreach ( (array) glob( $dir . '/m365-*' ) as $file ) {
|
|
wp_delete_file( $file );
|
|
}
|
|
@rmdir( $dir ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged, WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- best effort, may contain foreign files.
|
|
}
|
|
|
|
// Transients: state records and JWKS cache.
|
|
$wpdb->query( // phpcs:ignore WordPress.DB.DirectDatabaseQuery
|
|
$wpdb->prepare(
|
|
"DELETE FROM {$wpdb->options} WHERE option_name LIKE %s OR option_name LIKE %s",
|
|
$wpdb->esc_like( '_transient_m365_login_' ) . '%',
|
|
$wpdb->esc_like( '_transient_timeout_m365_login_' ) . '%'
|
|
)
|
|
);
|
|
}
|
|
|
|
if ( is_multisite() ) {
|
|
$m365_login_site_ids = get_sites( array( 'fields' => 'ids', 'number' => 0 ) );
|
|
foreach ( $m365_login_site_ids as $m365_login_site_id ) {
|
|
switch_to_blog( $m365_login_site_id );
|
|
m365_login_uninstall_site();
|
|
restore_current_blog();
|
|
}
|
|
} else {
|
|
m365_login_uninstall_site();
|
|
}
|
|
|
|
// User meta is global. Imported accounts stay; copied profile fields (m365_*) are kept as ordinary user data.
|
|
foreach ( array( '_m365_login_oid', '_m365_login_last_login', '_m365_login_synced', '_m365_login_disabled', '_m365_login_last_sync', '_m365_login_photo', '_m365_login_tid' ) as $m365_login_meta_key ) {
|
|
delete_metadata( 'user', 0, $m365_login_meta_key, '', true );
|
|
}
|