Dalibor Votruba 0a3f78f33e qdr-temporary-shared-storage: update tranlsations 1 年之前
..
@documentation 1a3b4888d9 qdr-temporary-shared-storage: change implementation to oop 1 年之前
admin 1a3b4888d9 qdr-temporary-shared-storage: change implementation to oop 1 年之前
includes 1a3b4888d9 qdr-temporary-shared-storage: change implementation to oop 1 年之前
languages 0a3f78f33e qdr-temporary-shared-storage: update tranlsations 1 年之前
public 1a3b4888d9 qdr-temporary-shared-storage: change implementation to oop 1 年之前
rest-api 1a3b4888d9 qdr-temporary-shared-storage: change implementation to oop 1 年之前
instructions.txt 4ee6acfe98 qdr-temporary-shared-storage: add initial version 1.0.0 1 年之前
qdr-temporary-shared-storage.php 1a3b4888d9 qdr-temporary-shared-storage: change implementation to oop 1 年之前
readme.md 1a3b4888d9 qdr-temporary-shared-storage: change implementation to oop 1 年之前
uninstall.php 1a3b4888d9 qdr-temporary-shared-storage: change implementation to oop 1 年之前

readme.md

QDR - Temporary Shared Storage (via REST API)

Version: 1.0.0

A WordPress plugin that extends functionality for REST API interface to upload temporary media files and provide permalink to password-protected download pages.

Description

QDR Temporary Shared Storage enables secure sharing of large files through WordPress with complete control over access and expiration. This plugin is ideal for businesses that need to share sensitive documents, large media files, or private content with clients or team members for a limited time.

Requirements

  • WordPress 6.8.1 or higher
  • PHP 8.2 or higher
  • WooCommerce 9.0 or higher (if WooCommerce integration is used)

Features

  • REST API Integration: Upload and manage files programmatically
  • Chunked File Uploads: Support for uploading large files in chunks
  • Password Protection: Secure access to shared files
  • Automatic Expiration: Files automatically expire and are removed after their expiration date
  • Download Tracking: Track number of downloads and last download time
  • Admin Interface: Manage shared files through a user-friendly admin interface
  • Custom Reference Codes: Add reference codes for additional verification
  • Storage Management: Set limits on total storage used by shared files
  • WordPress Media Library Integration: Files are properly registered in WordPress media system

Installation

  1. Upload the qdr-temporary-shared-storage directory to the /wp-content/plugins/ directory
  2. Activate the plugin through the 'Plugins' menu in WordPress
  3. Configure the plugin settings in 'Settings > Shared Storage'

Usage

Admin Interface

  • Media > Shared Storage: Manage all shared files
  • Settings > Shared Storage: Configure plugin settings

Shortcode

To display a download form for a specific file:

[qdr_tss_download id="123"]

To display a generic download form where users can enter an ID, password, and reference:

[qdr_tss_download]

REST API

The plugin provides a complete REST API for programmatically uploading, managing, and downloading shared files.

Authentication

All API requests require an API key sent in the X-Api-Key HTTP header. You can generate or view your API key in the plugin settings page at "Settings > Shared Storage".

API Endpoints

Upload Endpoints

  • POST /wp-json/qdr-tss/v1/upload/init: Initialize chunked upload
  • POST /wp-json/qdr-tss/v1/upload/chunk: Upload a file chunk
  • POST /wp-json/qdr-tss/v1/upload/finalize: Finalize upload and create file record

Media Endpoints

  • GET /wp-json/qdr-tss/v1/media: Get list of shared files
  • GET /wp-json/qdr-tss/v1/media/{media_id}: Get single file details
  • PUT /wp-json/qdr-tss/v1/media/{media_id}: Update file properties
  • DELETE /wp-json/qdr-tss/v1/media/{media_id}: Delete a file

API Example (PHP)

// Initialize chunked upload
$ch = curl_init('https://www.example.com/wp-json/qdr-tss/v1/upload/init');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-Api-Key: your-api-key-here'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
    'original_file_name' => 'example.pdf',
    'file_size' => filesize('path/to/example.pdf')
]);
$response = json_decode(curl_exec($ch));
curl_close($ch);

$upload_id = $response->upload_id;

// Upload chunks
$file = fopen('path/to/example.pdf', 'rb');
$chunk_size = 1024 * 1024; // 1MB chunks
$total_size = filesize('path/to/example.pdf');
$total_chunks = ceil($total_size / $chunk_size);

for ($i = 0; $i < $total_chunks; $i++) {
    $chunk_data = fread($file, $chunk_size);
    
    // Upload the chunk
    // ...
}

// Finalize upload
$ch = curl_init('https://www.example.com/wp-json/qdr-tss/v1/upload/finalize');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
    'X-Api-Key: your-api-key-here'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, [
    'upload_id' => $upload_id,
    'total_chunks' => $total_chunks,
    'original_file_name' => 'example.pdf',
    'description' => 'Example PDF file',
    'message' => 'Please review this document',
    'password' => 'secure_password',
    'reference' => 'REF123',
    'active_to' => date('Y-m-d H:i:s', strtotime('+30 days'))
]);
$response = json_decode(curl_exec($ch));
curl_close($ch);

// Get download link and media ID
$media_id = $response->media_id;
$permalink = $response->permalink;

REST API Documentation

Authentication

All API requests require an API key sent in the X-Api-Key HTTP header. You can generate or view your API key in the plugin settings page at "Settings > Shared Storage".

API Base URL

The base URL for all API endpoints is:

https://your-wordpress-site.com/wp-json/qdr-tss/v1/

Endpoints

1. Initialize Chunked Upload

Prepares the server for a chunked upload.

  • Endpoint: /upload/init
  • Method: POST
  • Parameters:

    • original_file_name: The original name of the file being uploaded
    • file_size: The total size of the file in bytes
  • Response:

    {
    "upload_id": "5f8d7a6b9c3e",
    "status": "initialized"
    }
    

2. Upload Chunk

Uploads a single chunk of the file.

  • Endpoint: /upload/chunk
  • Method: POST
  • Parameters:

    • upload_id: The upload ID received from the initialization
    • chunk_index: The index of the current chunk (starting from 0)
    • total_chunks: The total number of chunks
    • chunk: The chunk file data (multipart/form-data)
  • Response:

    {
    "upload_id": "5f8d7a6b9c3e",
    "chunk_index": 0,
    "status": "chunk_uploaded"
    }
    

3. Finalize Upload

Combines all chunks and creates the file record.

  • Endpoint: /upload/finalize
  • Method: POST
  • Parameters:

    • upload_id: The upload ID received from the initialization
    • total_chunks: The total number of chunks
    • original_file_name: The original name of the file
    • description: Description of the file
    • message: Custom message (optional)
    • active_from: Date from which permalink will be active (optional, defaults to current time)
    • active_to: Date until which permalink will be active (optional, defaults to 30 days from now)
    • password: Password for download protection
    • reference: Custom reference value (optional)
  • Response:

    {
    "media_id": "1621234567",
    "permalink": "https://your-wordpress-site.com/shared-file-download/1621234567",
    "shortcode": "[qdr_tss_download id=\"1621234567\"]"
    }
    

4. Get File Details

Retrieves details about a specific file.

  • Endpoint: /media/{media_id}
  • Method: GET

  • Response:

    {
    "id": 1,
    "media_id": "1621234567",
    "original_file_name": "example.pdf",
    "description": "Example document",
    "message": "Please review this document",
    "active_from": "2025-05-19 15:30:00",
    "active_to": "2025-06-18 15:30:00",
    "reference": "REF123",
    "upload_date": "2025-05-19 15:30:00",
    "download_count": 5,
    "last_download": "2025-05-20 10:45:30",
    "file_size": 1048576
    }
    

5. Update File

Updates properties of a specific file.

  • Endpoint: /media/{media_id}
  • Method: PUT
  • Parameters:

    • message: Custom message (optional)
    • active_from: Date from which permalink will be active (optional)
    • active_to: Date until which permalink will be active (optional)
    • reference: Custom reference value (optional)
    • description: File description (optional)
  • Response: Updated file details (same format as Get File Details)

6. Delete File

Deletes a specific file.

  • Endpoint: /media/{media_id}
  • Method: DELETE

  • Response:

    {
    "deleted": true,
    "media_id": "1621234567"
    }
    

7. List Files

Retrieves a list of files with pagination and filtering.

  • Endpoint: /media
  • Method: GET
  • Parameters:

    • page: Page number (optional, default: 1)
    • per_page: Items per page (optional, default: 10)
    • orderby: Field to sort by (optional, default: "upload_date")
    • order: Sort order (optional, default: "DESC")
    • original_file_name: Filter by file name (optional)
    • reference: Filter by reference (optional)
  • Response:

    [
    {
      "id": 1,
      "media_id": "1621234567",
      "original_file_name": "example.pdf",
      "description": "Example document",
      "message": "Please review this document",
      "active_from": "2025-05-19 15:30:00",
      "active_to": "2025-06-18 15:30:00",
      "reference": "REF123",
      "upload_date": "2025-05-19 15:30:00",
      "download_count": 5,
      "last_download": "2025-05-20 10:45:30",
      "file_size": 1048576
    },
    ...
    ]
    

Code Examples

PHP Example: Complete File Upload

<?php
/**
 * Example of uploading a file using chunked upload
 */
function uploadSharedFile($file_path, $description, $password, $reference = '') {
    $api_key = 'your-api-key-here';
    $api_url = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
    
    // File information
    $file_name = basename($file_path);
    $file_size = filesize($file_path);
    $chunk_size = 1024 * 1024; // 1MB chunks
    $total_chunks = ceil($file_size / $chunk_size);
    
    // Step 1: Initialize upload
    $ch = curl_init($api_url . '/upload/init');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'X-Api-Key: ' . $api_key,
        'Content-Type: application/json'
    ]);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'original_file_name' => $file_name,
        'file_size' => $file_size
    ]));
    
    $response = curl_exec($ch);
    curl_close($ch);
    
    $init_result = json_decode($response, true);
    if (!isset($init_result['upload_id'])) {
        throw new Exception('Failed to initialize upload: ' . $response);
    }
    
    $upload_id = $init_result['upload_id'];
    
    // Step 2: Upload chunks
    $file = fopen($file_path, 'rb');
    
    for ($i = 0; $i < $total_chunks; $i++) {
        // Create a temporary file for the chunk
        $chunk_file = tempnam(sys_get_temp_dir(), 'chunk');
        $chunk_handle = fopen($chunk_file, 'wb');
        
        // Read chunk from original file
        $chunk_data = fread($file, $chunk_size);
        fwrite($chunk_handle, $chunk_data);
        fclose($chunk_handle);
        
        // Upload the chunk
        $ch = curl_init($api_url . '/upload/chunk');
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        curl_setopt($ch, CURLOPT_HTTPHEADER, [
            'X-Api-Key: ' . $api_key
        ]);
        
        $post_data = [
            'upload_id' => $upload_id,
            'chunk_index' => $i,
            'total_chunks' => $total_chunks,
            'chunk' => new CURLFile($chunk_file, 'application/octet-stream', 'chunk')
        ];
        
        curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
        $response = curl_exec($ch);
        curl_close($ch);
        
        // Delete temporary chunk file
        unlink($chunk_file);
        
        $chunk_result = json_decode($response, true);
        if (!isset($chunk_result['status']) || $chunk_result['status'] !== 'chunk_uploaded') {
            throw new Exception('Failed to upload chunk ' . $i . ': ' . $response);
        }
        
        // Display progress
        echo "Uploaded chunk " . ($i + 1) . " of " . $total_chunks . "\n";
    }
    
    fclose($file);
    
    // Step 3: Finalize upload
    $ch = curl_init($api_url . '/upload/finalize');
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, [
        'X-Api-Key: ' . $api_key,
        'Content-Type: application/json'
    ]);
    
    // Set expiration to 30 days from now
    $active_to = date('Y-m-d H:i:s', strtotime('+30 days'));
    
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
        'upload_id' => $upload_id,
        'total_chunks' => $total_chunks,
        'original_file_name' => $file_name,
        'description' => $description,
        'message' => 'Uploaded via API example',
        'password' => $password,
        'reference' => $reference,
        'active_to' => $active_to
    ]));
    
    $response = curl_exec($ch);
    curl_close($ch);
    
    $final_result = json_decode($response, true);
    if (!isset($final_result['media_id'])) {
        throw new Exception('Failed to finalize upload: ' . $response);
    }
    
    return $final_result;
}

JavaScript Example: Chunked File Upload

/**
 * Uploads a file using chunked upload
 * @param {File} file The file to upload
 * @param {string} description File description
 * @param {string} password Password for download protection
 * @param {string} reference Optional reference
 * @param {function} progressCallback Callback for upload progress
 * @returns {Promise} Promise resolving to the upload result
 */
async function uploadSharedFile(file, description, password, reference = '', progressCallback = null) {
    const apiKey = 'your-api-key-here';
    const apiUrl = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
    
    const chunkSize = 1024 * 1024; // 1MB chunks
    const totalChunks = Math.ceil(file.size / chunkSize);
    
    // Step 1: Initialize upload
    const initResponse = await fetch(`${apiUrl}/upload/init`, {
        method: 'POST',
        headers: {
            'X-Api-Key': apiKey,
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            original_file_name: file.name,
            file_size: file.size
        })
    });
    
    const initResult = await initResponse.json();
    if (!initResult.upload_id) {
        throw new Error('Failed to initialize upload');
    }
    
    const uploadId = initResult.upload_id;
    
    // Step 2: Upload chunks
    for (let i = 0; i < totalChunks; i++) {
        const start = i * chunkSize;
        const end = Math.min(file.size, start + chunkSize);
        const chunk = file.slice(start, end);
        
        const formData = new FormData();
        formData.append('upload_id', uploadId);
        formData.append('chunk_index', i);
        formData.append('total_chunks', totalChunks);
        formData.append('chunk', chunk);
        
        const chunkResponse = await fetch(`${apiUrl}/upload/chunk`, {
            method: 'POST',
            headers: {
                'X-Api-Key': apiKey
            },
            body: formData
        });
        
        const chunkResult = await chunkResponse.json();
        if (!chunkResult.status || chunkResult.status !== 'chunk_uploaded') {
            throw new Error(`Failed to upload chunk ${i}`);
        }
        
        // Update progress
        if (progressCallback) {
            progressCallback((i + 1) / totalChunks * 100);
        }
    }
    
    // Step 3: Finalize upload
    const activeToDate = new Date();
    activeToDate.setDate(activeToDate.getDate() + 30); // 30 days from now
    const activeTo = activeToDate.toISOString().slice(0, 19).replace('T', ' ');
    
    const finalizeResponse = await fetch(`${apiUrl}/upload/finalize`, {
        method: 'POST',
        headers: {
            'X-Api-Key': apiKey,
            'Content-Type': 'application/json'
        },
        body: JSON.stringify({
            upload_id: uploadId,
            total_chunks: totalChunks,
            original_file_name: file.name,
            description: description,
            message: 'Uploaded via JavaScript API example',
            password: password,
            reference: reference,
            active_to: activeTo
        })
    });
    
    const finalResult = await finalizeResponse.json();
    if (!finalResult.media_id) {
        throw new Error('Failed to finalize upload');
    }
    
    return finalResult;
}

Error Handling

All API endpoints return standard HTTP status codes:

  • 200 OK: Request succeeded
  • 201 Created: Resource created successfully
  • 400 Bad Request: Invalid request parameters
  • 401 Unauthorized: Missing API key
  • 403 Forbidden: Invalid API key
  • 404 Not Found: Resource not found
  • 500 Internal Server Error: Server error

Error responses include a message explaining the error:

{
  "code": "qdr_tss_not_found",
  "message": "Media not found.",
  "data": {
    "status": 404
  }
}

Best Practices

  1. Always use chunked uploads for large files
  2. Set reasonable expiration dates for files to avoid cluttering storage
  3. Use unique reference codes to help identify files
  4. Regularly check and purge expired files using the admin interface
  5. Include descriptive file names and messages for better user experience

Configuration Options

API Key

Generate or specify an API key for REST API authentication.

Media Category

Specify a category to assign to uploaded media files.

Maximum Storage Size

Set the maximum total storage size for all shared files.

Security Considerations

  • All shared files are password-protected
  • Files are stored in a protected directory that cannot be accessed directly
  • Automatic expiration prevents unauthorized access to outdated files
  • API authentication through an API key ensures only authorized applications can upload files

License

GPL v2 or later - https://www.gnu.org/licenses/gpl-2.0.html

Author

Dalibor Votruba