Settings > Privacy) is cleaned: its Aiwend tables, aiwend_* options and * transients and capabilities. The scheduled job hook (aiwend_run_jobs) is removed on every site either way: retained data does not mean * a retained cron event, and reinstalling schedules a new one. Posts, pages, media, users, WooCommerce data and everything Aiwend does not own are * never touched. The encryption key file is removed only when no site on this installation still keeps Aiwend data. */ if (!defined('WP_UNINSTALL_PLUGIN')) { exit; } require_once __DIR__ . '/vendor/autoload.php'; (static function (): void { global $wpdb; // Returns true only when this site's data was removed completely. A step that fails does not stop the others (the options, capabilities and // scheduled hook are still cleaned), but then the administrator's flag stays on, so deleting the plugin again retries, and the key file is kept. $clean = static function () use ($wpdb): bool { // The recurring job event is scheduling, not user data: it goes whether or not the site keeps its data (a kept event would only // be dropped by WordPress at its next run, with a log line, because the plugin that registers its schedule is gone). $cronCleared = true; try { wp_clear_scheduled_hook('aiwend_run_jobs'); } catch (\Throwable $e) { $cronCleared = false; } $flag = get_option(\Aiwend\Lifecycle\Uninstaller::OPTION, false); if (!in_array($flag, [true, '1', 1], true)) { return false; } $uninstaller = new \Aiwend\Lifecycle\Uninstaller(new \Aiwend\Db\WpdbConnection($wpdb), $wpdb->prefix, $wpdb->options); $ok = $cronCleared; $steps = [ static function () use ($uninstaller): void { $uninstaller->dropTables(); }, static function () use ($uninstaller): void { $uninstaller->deleteOptions(true); }, static function (): void { // Content Studio (Stage E1): pending proposals, failed briefs and the idempotency marker on saved drafts. The drafts themselves are ordinary posts and stay. \Aiwend\WordPress\ContentStudioRetention::uninstall(get_current_blog_id()); // metadata only: no post is deleted \Aiwend\WordPress\WooCommerceAiRetention::uninstall(get_current_blog_id()); // WooCommerce AI (Stage E2): pending proposals only; no product is touched }, static function (): void { foreach (\Aiwend\Policy\Capabilities::all() as $cap) { foreach (wp_roles()->role_objects as $role) { $role->remove_cap($cap); } } }, ]; foreach ($steps as $step) { try { $step(); } catch (\Throwable $e) { $ok = false; // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- one plain, non-secret line when an uninstall step fails error_log('Aiwend uninstall: a cleanup step failed (' . get_class($e) . '). Some data may remain; delete the plugin again to retry.'); } } try { if ($ok && !$uninstaller->hasData()) { $uninstaller->deleteFlag(); } else { $ok = false; } } catch (\Throwable $e) { $ok = false; } wp_cache_flush(); return $ok; }; $keepsData = false; if (is_multisite()) { foreach (get_sites(['fields' => 'ids', 'number' => 0]) as $siteId) { switch_to_blog((int) $siteId); if (!$clean()) { $keepsData = true; // this site chose to keep its data (or never had any): the key file may still be needed } restore_current_blog(); } } else { $keepsData = !$clean(); } if (!$keepsData) { \Aiwend\Secrets\KeyFile::forLocation(\Aiwend\Secrets\KeyLocation::forWordPress())->delete(); } })();