Complete folder protection and file delivery system for WordPress. Protects upload directories with .htaccess rules, verifies protection is working, and provides secure file streaming with range support and server optimizations.
- 🔒 Folder Protection - Automatic .htaccess and index file creation
- 🚀 Smart File Delivery - Streaming with X-Sendfile, range requests, and chunked transfer
- 📁 Upload Organization - Auto-organize uploads by post type or custom logic
- ✅ Protection Testing - Actually verifies files are protected, not just file presence
- 🎯 Intelligent Defaults - PDFs display inline, ZIPs download, videos stream
- ⚡ Server Optimized - Supports Apache, Nginx, LiteSpeed with X-Sendfile/X-Accel-Redirect
composer require arraypress/wp-protected-folders- PHP 7.4 or later
- WordPress 5.0 or later
- arraypress/wp-file-utils (auto-installed)
// Register a protected folder
register_protected_folder( 'downloads', [
'allowed_types' => ['jpg', 'png'], // Allow preview images
'dated_folders' => true, // Organize by year/month
'auto_protect' => true, // Auto-protect on admin_init
'upload_filter' => 'download' // Auto-organize download post type uploads
] );
// Serve a protected file (automatically handles everything!)
deliver_protected_file( '/path/to/protected/file.pdf' );
// - PDFs display inline
// - ZIPs force download
// - Videos stream with range support
// - Uses X-Sendfile when available// Register a protected folder
register_protected_folder( string $id, array $config = [] ): ?Protector
// Get file system path
get_protected_folder_path( string $id, bool $dated = false ): string
// Get public URL
get_protected_folder_url( string $id, bool $dated = false ): string
// Check if protected (tests actual file access)
is_folder_protected( string $id, bool $force = false ): bool
// Get protector instance for advanced operations
get_protected_folder( string $id ): ?Protector// Deliver a protected file with automatic optimization
deliver_protected_file( string $file_path, array $options = [] ): void
// Create a reusable delivery instance
create_file_delivery( array $options = [] ): Delivery| Option | Type | Default | Description |
|---|---|---|---|
allowed_types |
array | ['jpg', 'jpeg', 'png', 'gif', 'webp'] |
File extensions allowed for public access |
dated_folders |
bool | true |
Organize uploads by year/month |
auto_protect |
bool | false |
Automatically protect on admin_init |
upload_filter |
mixed | null |
Post types, admin pages, or callback for upload filtering |
admin_notice_pages |
array | [] |
Admin page slugs to show protection notices |
The delivery system automatically optimizes based on file type:
| File Type | Behavior | Chunk Size |
|---|---|---|
| Display inline | 1MB | |
| Images | Display inline | 512KB |
| Video | Stream with range support | 2MB |
| Audio | Stream with range support | 1MB |
| Archives | Force download | 4MB |
| Documents | Force download | 1MB |
deliver_protected_file( $file_path, [
'filename' => 'custom-name.pdf', // Custom download name
'force_download' => true, // Override auto-detection
'enable_range' => false, // Disable range support
'chunk_size' => 4194304, // Custom chunk size (4MB)
'mime_type' => 'application/pdf' // Override MIME detection
] );Automatically detects and uses X-Sendfile for better performance:
- Apache: mod_xsendfile
- Nginx: X-Accel-Redirect (requires configuration)
- LiteSpeed: X-Sendfile
For Nginx, configure internal location:
location /internal/ {
internal;
alias /path/to/wp-content/uploads/;
}Then enable in WordPress:
add_filter( 'protected_folders_nginx_xsendfile', '__return_true' );
add_filter( 'protected_folders_nginx_internal_path', function() {
return '/internal/';
} );class Download_Manager {
public function __construct() {
add_action( 'init', [ $this, 'register_protection' ] );
add_action( 'init', [ $this, 'handle_download' ] );
}
public function register_protection() {
register_protected_folder( 'downloads', [
'allowed_types' => ['jpg', 'png'], // Preview images only
'dated_folders' => true,
'auto_protect' => true,
'upload_filter' => 'download' // Auto-organize uploads
] );
}
public function handle_download() {
if ( ! isset( $_GET['download_id'] ) ) {
return;
}
// Verify purchase, permissions, etc.
if ( ! $this->verify_access( $_GET['download_id'] ) ) {
wp_die( 'Access denied' );
}
// Get file path from your database
$file_path = $this->get_download_path( $_GET['download_id'] );
// Deliver the file - that's it!
deliver_protected_file( $file_path );
// Automatically handles:
// - MIME type detection
// - Optimal chunking
// - Range requests for resume
// - X-Sendfile when available
}
}class Product_Downloads {
public function serve_product_file( $product_id, $file_id ) {
$file = $this->get_product_file( $product_id, $file_id );
// Different behavior based on file type
switch ( $file['type'] ) {
case 'preview':
// Force inline display for previews
deliver_protected_file( $file['path'], [
'force_download' => false
] );
break;
case 'bonus':
// Custom filename for bonus content
deliver_protected_file( $file['path'], [
'filename' => "Bonus - {$file['title']}.pdf"
] );
break;
default:
// Let the system decide (PDF inline, ZIP download, etc.)
deliver_protected_file( $file['path'] );
}
}
}class Member_Content {
public function __construct() {
// Register protection for different content types
register_protected_folder( 'courses', [
'allowed_types' => [], // No direct access
'dated_folders' => false,
'auto_protect' => true
] );
}
public function stream_video( $lesson_id ) {
if ( ! $this->has_access( $lesson_id ) ) {
wp_die( 'Please upgrade your membership' );
}
$video_path = get_protected_folder_path( 'courses' ) . "/{$lesson_id}.mp4";
// Stream video with range support for seeking
deliver_protected_file( $video_path );
// Automatically:
// - Sends proper video headers
// - Supports range requests for seeking
// - Uses 2MB chunks for smooth playback
// - Enables resume if connection drops
}
public function download_resources( $resource_id ) {
$file_path = $this->get_resource_path( $resource_id );
// Force download even for PDFs
deliver_protected_file( $file_path, [
'force_download' => true,
'filename' => $this->get_nice_filename( $resource_id )
] );
}
}// Create reusable delivery instance with custom settings
$delivery = create_file_delivery( [
'chunk_size' => 4194304, // 4MB chunks
'enable_range' => true // Always enable range support
] );
// Use for multiple files with same settings
foreach ( $files as $file ) {
$delivery->stream( $file['path'], [
'filename' => $file['name']
] );
}
// Or use the class directly for full control
use ArrayPress\ProtectedFolders\Delivery;
$delivery = new Delivery();
$delivery->set_option( 'chunk_size', 8388608 ); // 8MB chunks
if ( $delivery->supports_xsendfile() ) {
// Server supports fast file serving!
}
$delivery->stream( $large_file );Automatically organize uploads to protected folders:
// Single post type
register_protected_folder( 'downloads', [
'upload_filter' => 'download'
] );
// Multiple post types
register_protected_folder( 'media', [
'upload_filter' => ['product', 'download']
] );
// Admin page
register_protected_folder( 'settings', [
'upload_filter' => 'admin:my-settings-page'
] );
// Custom logic
register_protected_folder( 'conditional', [
'upload_filter' => function() {
return isset( $_GET['special_upload'] );
}
] );Works automatically with .htaccess files.
Get rules for manual configuration:
$protector = get_protected_folder( 'downloads' );
echo $protector->get_nginx_rules();Get web.config rules:
$protector = get_protected_folder( 'downloads' );
echo $protector->get_iis_rules();Each protected folder gets multiple layers of protection:
.htaccess- Server-level denial with optional exceptionsindex.php- PHP silence file as fallbackindex.html- Empty HTML as additional fallback- Verification - Actually tests HTTP access to confirm protection
The delivery system automatically optimizes for performance:
- X-Sendfile - Offloads file serving to web server when available
- Smart Chunking - Optimal chunk sizes based on file type
- Range Support - Enables resume and video seeking
- Memory Management - Streams files without loading into memory
- Output Buffering - Proper buffer management for large files
- Complete Solution - Protection + delivery in one package
- Zero Configuration - Smart defaults that just work
- Performance Optimized - X-Sendfile, chunking, and range support
- WordPress Native - Follows WordPress coding standards
- Battle-Tested - Used in production e-commerce systems
- Minimal API - Simple functions for common tasks
- Extensible - Full class access for advanced needs
Contributions are welcome! Please feel free to submit a Pull Request.
GPL-2.0-or-later
Created and maintained by David Sherlock at ArrayPress.