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 );
}
}
}