`uploads_size_bytes`, plus whatever a registered file stage reports under its own id. */ public function scan(): array { $scan = [ 'uploads_size_bytes' => $this->uploads_size() ]; /** * Filters the scan, so a file stage added through `shiftora_file_stages` can * report its items. Each item carries a `size_bytes`. * * @param array $scan The uploads size, keyed `uploads_size_bytes`. */ $filtered = apply_filters( 'shiftora_site_scan', $scan ); return is_array( $filtered ) ? array_merge( $filtered, $scan ) : $scan; } private function uploads_size(): int { $upload_dir = wp_upload_dir(); $base = $upload_dir['basedir'] ?? ''; if ( ! $base || ! is_dir( $base ) ) { return 0; } return $this->dir_size( $base ); } /** * Directory size in bytes. Bounded to 10 levels and 200,000 files (a partial sum * past that), cached in a transient for 60 seconds per path, symlinks not followed. * * @param string $path Absolute directory path. */ private function dir_size( string $path ): int { if ( ! is_dir( $path ) ) { return 0; } $cache_key = 'shiftora_dir_size_' . md5( $path ); $cached = get_transient( $cache_key ); if ( false !== $cached ) { return (int) $cached; } $size = 0; $file_count = 0; $max_files = 200000; $max_depth = 10; $inner_it = new \RecursiveDirectoryIterator( $path, \FilesystemIterator::SKIP_DOTS | \FilesystemIterator::UNIX_PATHS ); $it = new \RecursiveIteratorIterator( $inner_it ); $it->setMaxDepth( $max_depth ); foreach ( $it as $file ) { if ( $file_count >= $max_files ) { break; } if ( $file->isFile() && ! $file->isLink() ) { $size += $file->getSize(); $file_count++; } } set_transient( $cache_key, $size, 300 ); return $size; } }