isDot() ) { continue; } $name = $item->getFilename(); if ( in_array( $name, [ '.htaccess', 'index.php', 'web.config' ], true ) ) { continue; } if ( $item->getMTime() > $cutoff ) { continue; } if ( $item->isDir() ) { self::delete_tree( $item->getPathname() ); } else { wp_delete_file( $item->getPathname() ); } } } /** Remove the whole working directory. Used on deactivation and uninstall. */ public static function purge_all(): void { foreach ( self::roots() as $root ) { if ( is_dir( $root ) ) { self::delete_tree( $root ); } } } /** * Every directory the plugin may have written to. * * The second entry is where versions up to 1.0.1 kept their work. It is * listed so an upgrade cleans up after them instead of leaving a database * dump behind. * * @return string[] */ private static function roots(): array { $roots = []; $current = Locations::work_root(); if ( '' !== $current ) { $roots[] = $current; } $roots[] = Locations::content_root() . '/ago-migrator-tmp'; return $roots; } /** Write the server-level denial files. Cheap enough to repeat. */ private static function protect( string $dir ): void { $files = [ '.htaccess' => "Require all denied\n\nDeny from all\n\n", 'index.php' => " "" . "\n", ]; foreach ( $files as $name => $contents ) { $path = $dir . '/' . $name; if ( ! file_exists( $path ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_put_contents_file_put_contents, WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents -- Runs before WP_Filesystem is available and must not prompt for credentials. file_put_contents( $path, $contents ); } } } /** * Whether a directory has nothing left in it. * * A file the process cannot delete (a different owner, a lock) leaves its * directory behind. Calling rmdir() anyway would raise a PHP warning on a * site with WP_DEBUG on, so the directory is simply left in place: the * restore writes over whatever is in it. */ private static function is_empty_dir( string $dir ): bool { return ! ( new \FilesystemIterator( $dir, \FilesystemIterator::SKIP_DOTS ) )->valid(); } private static function delete_tree( string $dir ): void { if ( ! is_dir( $dir ) ) { return; } $items = new \RecursiveIteratorIterator( new \RecursiveDirectoryIterator( $dir, \FilesystemIterator::SKIP_DOTS ), \RecursiveIteratorIterator::CHILD_FIRST ); foreach ( $items as $item ) { if ( $item->isDir() ) { if ( self::is_empty_dir( $item->getPathname() ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- WP_Filesystem offers no streaming-safe equivalent here. rmdir( $item->getPathname() ); } } else { wp_delete_file( $item->getPathname() ); } } if ( self::is_empty_dir( $dir ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir -- WP_Filesystem offers no streaming-safe equivalent here. rmdir( $dir ); } } }