' '' . 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
}
}