= $oldest_kept ) { continue; } if ( @unlink( $file ) ) { // phpcs:ignore WordPress.WP.AlternativeFunctions.unlink_unlink, WordPress.PHP.NoSilencedErrors.Discouraged // Keep the channel in the name so the maintenance log stays readable. $deleted[] = ltrim( str_replace( self::base_directory(), '', $file ), '/' ); } } return $deleted; } /** * Remove every log file and the directory itself. Used on uninstall. */ public static function purge(): void { $root = Plugin_Uploads::root(); if ( '' === $root || ! is_dir( $root ) ) { return; } // Globbed rather than taken from base_directory(): a rotated salt // changes the suffix, and a directory stranded by an older one has to // go as well. $log_directories = glob( trailingslashit( $root ) . self::DIRECTORY_PREFIX . '*', GLOB_ONLYDIR ); foreach ( is_array( $log_directories ) ? $log_directories : [] as $base ) { $channels = glob( trailingslashit( $base ) . '*', GLOB_ONLYDIR ); $channels = is_array( $channels ) ? $channels : []; // Everything inside our own directory goes, not only files matching // the log pattern: a single leftover would keep the directory alive // forever. The sweep covers the index.php and .htaccess guards too. foreach ( array_merge( $channels, [ $base ] ) as $directory ) { Plugin_Uploads::delete_files_in( $directory ); } // Channel sub directories first, the base can only go once empty. foreach ( $channels as $channel ) { @rmdir( $channel ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir, WordPress.PHP.NoSilencedErrors.Discouraged } @rmdir( $base ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_rmdir, WordPress.PHP.NoSilencedErrors.Discouraged } // The root is shared with the catalog exports, so it is not ours to // remove outright — it goes only once nothing else is left in it. Plugin_Uploads::remove_root_if_empty(); } /** * Every log file across all channels. * * @return string[] Absolute paths, oldest first. */ public static function files(): array { $base = self::base_directory(); if ( '' === $base || ! is_dir( $base ) ) { return []; } $matches = glob( trailingslashit( $base ) . '*/' . self::FILE_PREFIX . '*' . self::FILE_SUFFIX ); $files = is_array( $matches ) ? $matches : []; sort( $files ); return $files; } private static function date_from_path( string $path ): ?string { $name = basename( $path, self::FILE_SUFFIX ); $date = substr( $name, strlen( self::FILE_PREFIX ) ); return 1 === preg_match( '/^\d{4}-\d{2}-\d{2}$/', $date ) ? $date : null; } private static function today(): string { return gmdate( 'Y-m-d', self::now() ); } private static function now(): int { return (int) current_time( 'timestamp', true ); } }