' '' . PHP_EOL . "\tRequire all denied" . PHP_EOL . '' . PHP_EOL . '' . PHP_EOL . "\tOrder allow,deny" . PHP_EOL . "\tDeny from all" . PHP_EOL . '' . PHP_EOL, ]; foreach ( $guards as $name => $contents ) { $path = trailingslashit( $directory ) . $name; if ( file_exists( $path ) ) { continue; } @file_put_contents( $path, $contents ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file_put_contents, WordPress.PHP.NoSilencedErrors.Discouraged } } /** * Remove every file in a directory we own, so the directory itself can go. * * scandir rather than glob so dot files such as the .htaccess guard go too: * a single leftover would keep the directory alive forever. */ public static function delete_files_in( string $directory ): void { $entries = @scandir( $directory ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged foreach ( is_array( $entries ) ? $entries : [] as $entry ) { if ( '.' === $entry || '..' === $entry ) { continue; } $path = trailingslashit( $directory ) . $entry; if ( is_file( $path ) || is_link( $path ) ) { @unlink( $path ); // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink, WordPress.PHP.NoSilencedErrors.Discouraged } } } /** * Drop the shared root, but only once its last tenant has left. * * Logs and exports purge independently and in an order nothing guarantees — * uninstall even purges the logs, then the exports while the uninstall log * is still being written, then the logs again. Neither consumer may reach * for the shared parent itself, so both call this instead: it is a no-op * while anything else is still in there, and the last caller wins. */ public static function remove_root_if_empty(): void { $root = self::root(); if ( '' === $root || ! is_dir( $root ) ) { return; } $entries = @scandir( $root ); // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged $guards = [ '.', '..', 'index.php', '.htaccess' ]; foreach ( is_array( $entries ) ? $entries : [] as $entry ) { // Anything that is not one of our own guard files means a tenant is // still using the root. Leave it, guards included. if ( ! in_array( $entry, $guards, true ) ) { return; } } self::delete_files_in( $root ); @rmdir( $root ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir, WordPress.PHP.NoSilencedErrors.Discouraged } }