ソースを参照

qdr-temporary-shared-storage: fix activation mechanism, add validation ActiveFrom / ActiveTo. Update translations

Dalibor Votruba 1 年間 前
コミット
304f5e64ab

+ 163 - 111
qdr-temporary-shared-storage/@documentation/plugin-structure.md

@@ -21,15 +21,15 @@ qdr-temporary-shared-storage/
 ├── admin/                                 # Admin-related functionality
 │   ├── class-qdr-tss-admin.php            # Admin controller
-│   ├── class-qdr-tss-media-page.php       # Media page UI
-│   ├── class-qdr-tss-settings-page.php    # Settings page UI
+│   ├── class-qdr-tss-media-page.php       # Media page UI and AJAX handlers
+│   ├── class-qdr-tss-settings-page.php    # Settings page UI and AJAX handlers
 │   ├── partials/                          # Admin UI templates
 │   │   ├── media-page.php                 # Media listing page template
 │   │   └── settings-page.php              # Settings page template
 │   ├── js/
-│   │   └── admin.js                       # Admin JavaScript
+│   │   └── admin.js                       # Admin JavaScript (pagination, sorting, modals)
 │   └── css/
-│       └── admin.css                      # Admin styles
+│       └── admin.css                      # Admin styles (table, modals, buttons)
 ├── public/                                # Public-facing functionality
 │   ├── class-qdr-tss-public.php           # Public controller
@@ -53,7 +53,8 @@ qdr-temporary-shared-storage/
     ├── integration-guide.md               # Integration guide
     ├── plugin-structure.md                # Plugin structure documentation
     ├── rest-api-docs.md                   # REST API documentation
-    └── testing-guide.md                   # Testing guide
+    ├── testing-guide.md                   # Testing guide
+    └── readme.md                          # Complete plugin documentation
 ```
 
 ## Class Responsibilities
@@ -63,21 +64,23 @@ qdr-temporary-shared-storage/
 #### QDR_TSS (Main Plugin Class)
 - Initialize the plugin
 - Load dependencies
-- Register hooks
+- Register hooks for admin, public, REST API, and cron functionality
 - Define plugin-wide constants
 - Load translations
 
 #### QDR_TSS_Activator
 - Handle plugin activation
-- Create database tables
-- Create directories
-- Create .htaccess files
-- Set default options
-- Create download page
+- Create database tables with proper schema
+- Create file system directories and security files
+- Set default options including API key generation
+- Create download page with shortcode
+- Schedule cron job for cleanup
+- Add rewrite rules for pretty URLs
 
 #### QDR_TSS_Deactivator
 - Handle plugin deactivation
-- Clear scheduled hooks
+- Clear scheduled cron jobs
+- Flush rewrite rules
 
 #### QDR_TSS_Loader
 - Register actions and filters
@@ -87,98 +90,149 @@ qdr-temporary-shared-storage/
 - Load text domain
 - Enable internationalization
 
-#### QDR_TSS_DB_Manager
-- Create/update custom tables
-- Database migrations
-- Database queries for media items
+#### QDR_TSS_DB_Manager (Singleton)
+- Create/update custom tables with proper indexes
+- Database queries for media items with pagination and filtering
 - CRUD operations for media items
-- Storage size calculations
-
-#### QDR_TSS_File_Manager
-- Handle file operations
-- Create file directories
-- Manage chunked uploads
-- Delete files
-- Clean up empty directories
-
-#### QDR_TSS_Settings
+- Storage size calculations and limit checks
+- Handle expired items cleanup
+- Support for fileid-based operations
+
+#### QDR_TSS_File_Manager (Singleton)
+- Handle file operations and chunked uploads
+- Generate unique file IDs
+- Create dated directory structures
+- Manage chunked upload process (init, store chunks, finalize)
+- Delete files and cleanup empty directories
+- File system initialization with security
+
+#### QDR_TSS_Settings (Singleton)
 - Get/set plugin settings
 - Settings validation
-- Default settings
-- API key management
+- Default settings management
+- API key generation and validation
+- Storage management settings
 
 #### QDR_TSS_Cron_Manager
-- Register cron schedules
-- Handle expired files cleanup
-- Schedule checks
+- Register and manage cron schedules
+- Handle expired files cleanup (both files and database records)
+- Manual purge functionality
+- Automatic cleanup of empty directories
 
 ### Admin Classes
 
 #### QDR_TSS_Admin
-- Register admin menu items
-- Enqueue admin scripts/styles
-- Handle AJAX requests
+- Register admin menu items in Media and Settings
+- Enqueue admin scripts/styles with proper dependencies
+- Handle localization for JavaScript
 - Initialize admin hooks
 
 #### QDR_TSS_Media_Page
-- Render Media Shared Storage page
-- Handle media listing
-- Edit/delete functionality
-- AJAX handlers for media operations
+- Handle AJAX requests for media operations:
+  - Get items with pagination, sorting, and filtering
+  - Edit item properties
+  - Delete items
+  - Get item details for modals
+  - **NEW: Force activate functionality** - Reset active period starting from current time
+- Pagination and search functionality
+- File management operations
 
 #### QDR_TSS_Settings_Page
-- Render Settings page
-- Save settings
-- AJAX handlers for settings operations
-- Storage management
-- Handle purge expired files
+- Handle AJAX requests for settings operations:
+  - Save plugin settings
+  - Manual purge of expired files
+- Settings validation and management
 
 ### Public Classes
 
 #### QDR_TSS_Public
 - Register public scripts/styles
-- Public hooks
-- Create download page
+- Handle rewrite rules for pretty download URLs
+- Create download page if missing
+- Manage query variables for file downloads
 
 #### QDR_TSS_Shortcode
-- Register shortcodes
-- Render download forms
+- Register and handle `[qdr_tss_download]` shortcode
+- Support for both specific file ID and generic download form
 
 #### QDR_TSS_Download
-- Handle download requests
-- Verify passwords and references
-- Process file download
-- Update download stats
+- Handle download requests with password and reference verification
+- Verify file accessibility (active dates)
+- Process file downloads with proper headers
+- Update download statistics (count and last download time)
+- Support for both URL parameter and shortcode-based downloads
 
 ### REST API Classes
 
 #### QDR_TSS_REST_Controller
-- Base REST API functionality
-- Authentication
-- Permissions
+- Base REST API functionality and authentication
+- API key validation via X-Api-Key header
+- Permissions management for all endpoints
 
 #### QDR_TSS_Media_Controller
-- GET/PUT/DELETE media endpoints
-- List media collection
-- Media properties management
+- REST endpoints for media management:
+  - `GET /media` - List media with pagination and filtering
+  - `GET /media/{fileid}` - Get specific media details
+  - `PUT /media/{fileid}` - Update media properties
+  - `DELETE /media/{fileid}` - Delete media
+- Proper error handling and response formatting
+- Security: passwords are excluded from API responses
 
 #### QDR_TSS_Upload_Controller
-- Chunked upload endpoints
-- File validation
-- Upload initialization
-- Chunk handling
-- Upload finalization
+- REST endpoints for chunked file uploads:
+  - `POST /upload/init` - Initialize chunked upload
+  - `POST /upload/chunk` - Upload individual chunks
+  - `POST /upload/finalize` - Combine chunks and create media record
+- File validation and storage limit checks
+- Proper cleanup of temporary files
+
+## Key Features and Changes
+
+### Database Schema Updates
+- **fileid**: Changed from `media_id` to `fileid` for better clarity
+- Proper indexes on commonly queried fields
+- Support for tracking download statistics
+
+### Admin Interface Enhancements
+- **New Action Buttons**: 
+  - Copy Permalink button with direct clipboard functionality
+  - Force Activate button to reset active period
+- **Improved UI**: Better responsive design and modern styling
+- **Enhanced Search**: Filter by filename and reference
+- **Better Modals**: Improved modal system for viewing and editing
+
+### File Management Improvements
+- **Unique File ID Generation**: Improved algorithm using timestamp and random data
+- **Chunked Upload Support**: Complete implementation for large files
+- **Storage Management**: Automatic cleanup and size tracking
+- **Security**: Protected file storage with .htaccess rules
+
+### REST API Enhancements
+- **Consistent Naming**: Uses `fileid` instead of `media_id` throughout
+- **Better Error Handling**: Standardized error responses
+- **Security**: API key authentication for all endpoints
+- **File Operations**: Complete CRUD operations via REST API
+
+### Cron and Cleanup
+- **Automatic Cleanup**: Hourly cron job to remove expired files
+- **Manual Purge**: Admin interface button for immediate cleanup
+- **Directory Cleanup**: Removes empty directories after file deletion
+
+### Internationalization
+- **Complete Translation Support**: All user-facing strings are translatable
+- **Czech Translation**: Included as example
+- **Proper Text Domains**: Consistent use throughout the plugin
 
 ## File Dependencies
 
-Here's a list of the main file dependencies:
+Here's the updated dependency tree:
 
 1. **qdr-temporary-shared-storage.php** - Loads:
    - class-qdr-tss-activator.php
    - class-qdr-tss-deactivator.php
    - class-qdr-tss.php
 
-2. **class-qdr-tss.php** - Loads:
+2. **class-qdr-tss.php** - Loads all core components:
    - class-qdr-tss-loader.php
    - class-qdr-tss-i18n.php
    - class-qdr-tss-db-manager.php
@@ -186,54 +240,52 @@ Here's a list of the main file dependencies:
    - class-qdr-tss-settings.php
    - class-qdr-tss-cron-manager.php
    - functions.php
-   - class-qdr-tss-admin.php
-   - class-qdr-tss-media-page.php
-   - class-qdr-tss-settings-page.php
-   - class-qdr-tss-public.php
-   - class-qdr-tss-shortcode.php
-   - class-qdr-tss-download.php
-   - class-qdr-tss-rest-controller.php
-   - class-qdr-tss-media-controller.php
-   - class-qdr-tss-upload-controller.php
-
-3. **class-qdr-tss-admin.php** - Loads:
-   - admin/js/admin.js
+   - Admin classes (admin.php, media-page.php, settings-page.php)
+   - Public classes (public.php, shortcode.php, download.php)
+   - REST API classes (rest-controller.php, media-controller.php, upload-controller.php)
+
+3. **class-qdr-tss-admin.php** - Enqueues:
    - admin/css/admin.css
+   - admin/js/admin.js (with jQuery UI datepicker)
+   - Localized JavaScript with translations
 
-4. **class-qdr-tss-public.php** - Loads:
-   - public/js/public.js
+4. **class-qdr-tss-public.php** - Enqueues:
    - public/css/public.css
+   - public/js/public.js
 
-## Additional Files Needed
-
-Based on the review of the source code, the following files need to be created/renamed:
-
-1. **class-qdr-tss-loader.php** - Rename existing class-qdr-tss-file-loader.php to this name to match references in the code
-2. **admin/js/admin.js** - Create this file for admin JavaScript functionality (currently referenced but not included)
-
-## Database Schema
-
-The plugin uses a custom database table with the following schema:
-
-```sql
-CREATE TABLE {$wpdb->prefix}qdr_temporary_shared_storage (
-    id bigint(20) NOT NULL AUTO_INCREMENT,
-    media_id bigint(20) NOT NULL,
-    original_file_name varchar(255) NOT NULL,
-    description text NULL,
-    message text NULL,
-    active_from datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
-    active_to datetime DEFAULT '0000-00-00 00:00:00' NOT NULL,
-    password varchar(255) NOT NULL,
-    reference varchar(255) NULL,
-    upload_date datetime DEFAULT CURRENT_TIMESTAMP NOT NULL,
-    download_count int DEFAULT 0 NOT NULL,
-    last_download datetime DEFAULT '0000-00-00 00:00:00' NULL,
-    file_path varchar(255) NOT NULL,
-    file_size bigint(20) NOT NULL,
-    PRIMARY KEY  (id),
-    KEY media_id (media_id),
-    KEY reference (reference),
-    KEY active_to (active_to)
-)
-```
+## Security Considerations
+
+### File Storage Security
+- Files stored in protected directory with .htaccess rules
+- No direct file access - all downloads go through WordPress
+- Automatic cleanup of expired files
+
+### API Security
+- API key authentication required for all REST endpoints
+- Proper nonce verification for admin AJAX requests
+- Input sanitization and validation throughout
+
+### Download Security
+- Password protection for all downloads
+- Optional reference codes for additional security
+- Time-based access control (active from/to dates)
+- Download tracking for audit purposes
+
+## Performance Optimizations
+
+### Database
+- Proper indexing on frequently queried columns
+- Pagination for large datasets
+- Efficient queries for file listing and search
+
+### File Operations
+- Chunked uploads for large files
+- Temporary file cleanup
+- Efficient storage usage tracking
+
+### Admin Interface
+- AJAX-based operations for better user experience
+- Client-side pagination and sorting
+- Lazy loading of file details
+
+This structure provides a robust, secure, and user-friendly system for temporary file sharing with complete WordPress integration and comprehensive REST API support.

+ 72 - 29
qdr-temporary-shared-storage/@documentation/rest-api-docs.md

@@ -77,9 +77,9 @@ Combines all chunks and creates the file record.
 - **Response**:
   ```json
   {
-    "media_id": "1621234567",
-    "permalink": "https://your-wordpress-site.com/shared-file-download/1621234567",
-    "shortcode": "[qdr_tss_download id=\"1621234567\"]"
+    "fileid": "1621234567abcdef890",
+    "permalink": "https://your-wordpress-site.com/shared-file-download/1621234567abcdef890",
+    "shortcode": "[qdr_tss_download id=\"1621234567abcdef890\"]"
   }
   ```
 
@@ -87,14 +87,14 @@ Combines all chunks and creates the file record.
 
 Retrieves details about a specific file.
 
-- **Endpoint**: `/media/{media_id}`
+- **Endpoint**: `/media/{fileid}`
 - **Method**: `GET`
 
 - **Response**:
   ```json
   {
     "id": 1,
-    "media_id": "1621234567",
+    "fileid": "1621234567abcdef890",
     "original_file_name": "example.pdf",
     "description": "Example document",
     "message": "Please review this document",
@@ -112,7 +112,7 @@ Retrieves details about a specific file.
 
 Updates properties of a specific file.
 
-- **Endpoint**: `/media/{media_id}`
+- **Endpoint**: `/media/{fileid}`
 - **Method**: `PUT`
 - **Parameters**:
   - `message`: Custom message (optional)
@@ -127,14 +127,14 @@ Updates properties of a specific file.
 
 Deletes a specific file.
 
-- **Endpoint**: `/media/{media_id}`
+- **Endpoint**: `/media/{fileid}`
 - **Method**: `DELETE`
 
 - **Response**:
   ```json
   {
     "deleted": true,
-    "media_id": "1621234567"
+    "fileid": "1621234567abcdef890"
   }
   ```
 
@@ -157,7 +157,7 @@ Retrieves a list of files with pagination and filtering.
   [
     {
       "id": 1,
-      "media_id": "1621234567",
+      "fileid": "1621234567abcdef890",
       "original_file_name": "example.pdf",
       "description": "Example document",
       "message": "Please review this document",
@@ -168,8 +168,7 @@ Retrieves a list of files with pagination and filtering.
       "download_count": 5,
       "last_download": "2025-05-20 10:45:30",
       "file_size": 1048576
-    },
-    ...
+    }
   ]
   ```
 
@@ -288,7 +287,7 @@ function uploadSharedFile($file_path, $description, $password, $reference = '')
     curl_close($ch);
     
     $final_result = json_decode($response, true);
-    if (!isset($final_result['media_id'])) {
+    if (!isset($final_result['fileid'])) {
         throw new Exception('Failed to finalize upload: ' . $response);
     }
     
@@ -305,7 +304,7 @@ try {
     );
     
     echo "File uploaded successfully!\n";
-    echo "Media ID: " . $result['media_id'] . "\n";
+    echo "File ID: " . $result['fileid'] . "\n";
     echo "Permalink: " . $result['permalink'] . "\n";
     echo "Shortcode: " . $result['shortcode'] . "\n";
 } catch (Exception $e) {
@@ -407,7 +406,7 @@ async function uploadSharedFile(file, description, password, reference = '', pro
     });
     
     const finalResult = await finalizeResponse.json();
-    if (!finalResult.media_id) {
+    if (!finalResult.fileid) {
         throw new Error('Failed to finalize upload');
     }
     
@@ -433,7 +432,7 @@ document.getElementById('file-input').addEventListener('change', async (event) =
         );
         
         console.log('File uploaded successfully!');
-        console.log(`Media ID: ${result.media_id}`);
+        console.log(`File ID: ${result.fileid}`);
         console.log(`Permalink: ${result.permalink}`);
         console.log(`Shortcode: ${result.shortcode}`);
     } catch (error) {
@@ -504,7 +503,7 @@ echo "Total files: " . $result['total'] . "\n";
 echo "Total pages: " . $result['total_pages'] . "\n";
 
 foreach ($result['files'] as $file) {
-    echo "ID: " . $file['media_id'] . ", Name: " . $file['original_file_name'] . "\n";
+    echo "ID: " . $file['fileid'] . ", Name: " . $file['original_file_name'] . "\n";
 }
 ```
 
@@ -513,15 +512,15 @@ foreach ($result['files'] as $file) {
 ```javascript
 /**
  * Updates file properties
- * @param {string} mediaId The media ID
+ * @param {string} fileid The file ID
  * @param {object} properties Properties to update
  * @returns {Promise} Promise resolving to the updated file
  */
-async function updateSharedFile(mediaId, properties) {
+async function updateSharedFile(fileid, properties) {
     const apiKey = 'your-api-key-here';
     const apiUrl = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
     
-    const response = await fetch(`${apiUrl}/media/${mediaId}`, {
+    const response = await fetch(`${apiUrl}/media/${fileid}`, {
         method: 'PUT',
         headers: {
             'X-Api-Key': apiKey,
@@ -531,7 +530,7 @@ async function updateSharedFile(mediaId, properties) {
     });
     
     const result = await response.json();
-    if (!result.media_id) {
+    if (!result.fileid) {
         throw new Error('Failed to update file');
     }
     
@@ -541,7 +540,7 @@ async function updateSharedFile(mediaId, properties) {
 // Example usage
 async function exampleUpdateFile() {
     try {
-        const updatedFile = await updateSharedFile('1621234567', {
+        const updatedFile = await updateSharedFile('1621234567abcdef890', {
             message: 'Updated message',
             active_to: '2025-07-19 15:30:00',
             reference: 'UPDATED-REF'
@@ -574,22 +573,66 @@ Error responses include a message explaining the error:
 ```json
 {
   "code": "qdr_tss_not_found",
-  "message": "Media not found.",
+  "message": "File not found.",
   "data": {
     "status": 404
   }
 }
 ```
 
+## Important Changes from Previous Version
+
+### Key Updates
+
+1. **File ID Format**: Changed from simple `media_id` to `fileid` with a more complex alphanumeric format for better uniqueness
+2. **Response Format**: All endpoints now return `fileid` instead of `media_id`
+3. **Security**: Enhanced API key validation and better error messages
+4. **File Storage**: Improved file path generation with date-based directory structure
+5. **Chunked Uploads**: More robust chunk handling with better error recovery
+
+### Breaking Changes
+
+- **API Response Field**: `media_id` has been replaced with `fileid` in all responses
+- **URL Parameters**: Download URLs now use the new `fileid` format
+- **Database Schema**: Internal changes to support the new file ID system
+
+### New Features
+
+- **Force Activate**: Admin interface includes new functionality to reset active periods
+- **Enhanced Error Handling**: More descriptive error messages for debugging
+- **Better File Management**: Improved cleanup and storage management
+- **Responsive UI**: Better mobile support for admin interface
+
 ## Rate Limiting
 
-There are currently no rate limits on the API, but excessive usage may be throttled in the future.
+There are currently no rate limits on the API, but excessive usage may be throttled in the future. Consider implementing your own rate limiting on the client side for production applications.
 
 ## 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
-
+1. **Always use chunked uploads** for files larger than 1MB to avoid timeout issues
+2. **Set reasonable expiration dates** to prevent storage bloat
+3. **Use unique reference codes** to help organize and identify files
+4. **Implement proper error handling** in your client applications
+5. **Validate file types and sizes** before upload to provide better user experience
+6. **Store file IDs securely** in your application database for future reference
+7. **Monitor storage usage** and implement cleanup procedures
+8. **Use HTTPS** for all API communications to protect sensitive data
+
+## Storage Management
+
+The plugin includes built-in storage management features:
+
+- **Storage Limits**: Configure maximum total storage size in plugin settings
+- **Automatic Cleanup**: Hourly cron job removes expired files
+- **Manual Purge**: Admin interface button for immediate cleanup
+- **Storage Tracking**: Real-time storage usage monitoring
+
+## Security Features
+
+- **API Key Authentication**: All endpoints require valid API key
+- **Password Protection**: Downloaded files require password verification
+- **Reference Codes**: Optional additional security layer
+- **Time-based Access**: Files only accessible during active period
+- **Protected Storage**: Files stored outside web-accessible directory
+- **Download Tracking**: Audit trail of all file access
+    

+ 548 - 297
qdr-temporary-shared-storage/@documentation/testing-guide.md

@@ -1,124 +1,274 @@
 # QDR Temporary Shared Storage - Testing Guide
 
-This document outlines a comprehensive testing strategy for the QDR Temporary Shared Storage plugin to ensure all components function correctly after refactoring.
+This document outlines a comprehensive testing strategy for the QDR Temporary Shared Storage plugin to ensure all components function correctly after the latest updates and refactoring.
 
 ## Manual Testing Procedures
 
 ### 1. Installation and Activation Testing
 
-- [ ] Upload plugin to a test WordPress site
+- [ ] Upload plugin to a test WordPress site (WordPress 6.8.1+ and PHP 8.2+)
 - [ ] Activate the plugin
 - [ ] Verify that activation creates:
-  - [ ] The custom database table
-  - [ ] The required upload directories
-  - [ ] The download page
-  - [ ] The default plugin settings
+  - [ ] The custom database table with proper schema (including `fileid` field)
+  - [ ] The required upload directories with `.htaccess` protection
+  - [ ] The download page with `[qdr_tss_download]` shortcode
+  - [ ] Default plugin settings including auto-generated API key
+  - [ ] Scheduled cron job for cleanup
 
 ### 2. Admin Interface Testing
 
-#### 2.1 Settings Page
+#### 2.1 Settings Page (Settings > Shared Storage)
 
 - [ ] Navigate to Settings > Shared Storage
-- [ ] Verify all settings fields are displayed correctly
-- [ ] Test generating a new API key
+- [ ] Verify all settings fields are displayed correctly:
+  - [ ] API Key field with current key displayed
+  - [ ] Media Category field
+  - [ ] Maximum Storage Size field with current usage display
+- [ ] Test generating a new API key using "Generate New Key" button
 - [ ] Change settings values and save
-- [ ] Refresh page and verify settings were saved
-- [ ] Test the "Purge Expired Files" button
+- [ ] Refresh page and verify settings were saved correctly
+- [ ] Test the "Purge Expired Files" button functionality
+- [ ] Verify storage usage bar displays correctly
 
-#### 2.2 Media Page
+#### 2.2 Media Page (Media > Shared Storage)
 
 - [ ] Navigate to Media > Shared Storage
 - [ ] Verify the table loads correctly (initially empty)
-- [ ] Upload a file through the REST API (see REST API testing)
-- [ ] Verify the file appears in the table
-- [ ] Test sorting by different columns
-- [ ] Test pagination (upload multiple files if needed)
+- [ ] Upload files through the REST API (see REST API testing section)
+- [ ] Verify files appear in the table with correct data:
+  - [ ] File ID (new `fileid` format)
+  - [ ] Original file name
+  - [ ] Description and message
+  - [ ] Active from/to dates
+  - [ ] Reference
+  - [ ] Upload date
+  - [ ] Download count
+  - [ ] Last download
+- [ ] Test sorting by different columns (click column headers)
+- [ ] Test pagination with multiple files
 - [ ] Test searching by filename and reference
-- [ ] Test the "View Details" functionality
-- [ ] Test the "Edit" functionality:
-  - [ ] Change description, message, dates, reference
-  - [ ] Save and verify changes are applied
-- [ ] Test the "Delete" functionality
+- [ ] **Test new action buttons**:
+  - [ ] **Copy Permalink button** - verify it copies the correct URL to clipboard
+  - [ ] **Force Activate button** - verify it resets the active period from current time
+  - [ ] View Details button - verify modal opens with complete file information
+  - [ ] Edit button - verify modal allows property editing
+  - [ ] Delete button - verify confirmation dialog and file deletion
+
+#### 2.3 Enhanced Admin Features
+
+- [ ] Test responsive design on mobile devices
+- [ ] Verify modal dialogs work correctly
+- [ ] Test AJAX operations don't cause page refreshes
+- [ ] Verify loading indicators appear during operations
+- [ ] Test error handling in admin interface
 
 ### 3. REST API Testing
 
 #### 3.1 Authentication
 
-- [ ] Attempt API requests without an API key (should fail)
-- [ ] Attempt API requests with an invalid API key (should fail)
+- [ ] Attempt API requests without an API key (should return 403)
+- [ ] Attempt API requests with an invalid API key (should return 403)
 - [ ] Attempt API requests with the correct API key (should succeed)
-
-#### 3.2 File Upload
-
-- [ ] Test the complete chunked upload process:
-  - [ ] Initialize upload
-  - [ ] Upload chunks
-  - [ ] Finalize upload
-- [ ] Verify the file is stored in the correct location
-- [ ] Verify the database record is created correctly
-- [ ] Test with various file sizes:
-  - [ ] Small file (< 1MB)
-  - [ ] Medium file (1-10MB)
-  - [ ] Large file (> 10MB)
-- [ ] Test with different file types:
-  - [ ] PDF
-  - [ ] Image (JPEG, PNG)
-  - [ ] Office document (DOCX, XLSX)
-  - [ ] ZIP archive
-
-#### 3.3 File Management
-
-- [ ] Test retrieving file details via API
-- [ ] Test updating file properties via API
-- [ ] Test deleting a file via API
-- [ ] Test retrieving file collections with various query parameters:
-  - [ ] Pagination
-  - [ ] Sorting
-  - [ ] Filtering
+- [ ] Test API key validation with various header formats
+
+#### 3.2 File Upload (Chunked)
+
+Test the complete chunked upload process:
+
+- [ ] **Initialize upload** (`POST /upload/init`):
+  - [ ] Valid file name and size
+  - [ ] Invalid parameters (missing fields)
+  - [ ] File size exceeding storage limit
+- [ ] **Upload chunks** (`POST /upload/chunk`):
+  - [ ] Sequential chunk upload
+  - [ ] Out-of-order chunk upload
+  - [ ] Missing chunks
+  - [ ] Invalid chunk data
+- [ ] **Finalize upload** (`POST /upload/finalize`):
+  - [ ] All required fields provided
+  - [ ] Missing required fields
+  - [ ] Invalid date formats
+  - [ ] Verify file stored in correct date-based directory structure
+  - [ ] Verify database record created with new `fileid` format
+  - [ ] Verify response includes `fileid`, `permalink`, and `shortcode`
+
+Test with various file types and sizes:
+- [ ] Small file (< 1MB) - should work with single chunk
+- [ ] Medium file (1-10MB) - test multiple chunks
+- [ ] Large file (> 10MB) - test many chunks
+- [ ] PDF documents
+- [ ] Image files (JPEG, PNG, GIF)
+- [ ] Office documents (DOCX, XLSX, PPTX)
+- [ ] Archive files (ZIP, RAR)
+- [ ] Text files
+- [ ] Video files (if within size limits)
+
+#### 3.3 File Management API
+
+- [ ] **Get file details** (`GET /media/{fileid}`):
+  - [ ] Valid file ID
+  - [ ] Invalid/non-existent file ID
+  - [ ] Verify password is not included in response
+- [ ] **Update file properties** (`PUT /media/{fileid}`):
+  - [ ] Update message
+  - [ ] Update active dates
+  - [ ] Update reference
+  - [ ] Update description
+  - [ ] Invalid date formats
+  - [ ] Empty update request
+- [ ] **Delete file** (`DELETE /media/{fileid}`):
+  - [ ] Valid file ID
+  - [ ] Invalid/non-existent file ID
+  - [ ] Verify file deleted from storage
+  - [ ] Verify database record removed
+- [ ] **List files** (`GET /media`):
+  - [ ] Default pagination
+  - [ ] Custom pagination parameters
+  - [ ] Sorting by different fields
+  - [ ] Filtering by filename
+  - [ ] Filtering by reference
+  - [ ] Combined filters and sorting
 
 ### 4. Public Interface Testing
 
-- [ ] Verify the download page exists and functions correctly
-- [ ] Test the download process with correct password and reference
-- [ ] Test the download process with incorrect password (should fail)
-- [ ] Test the download process with incorrect reference (should fail)
-- [ ] Test with expired files (should not allow download)
-- [ ] Test with future activation date (should not allow download)
-- [ ] Verify download count and last download are updated correctly
-
-### 5. Scheduled Tasks Testing
-
-- [ ] Manually trigger cron job to purge expired files
-- [ ] Verify expired files are deleted from storage
-- [ ] Verify expired records are deleted from database
-- [ ] Check that non-expired files are not affected
-
-### 6. Edge Cases Testing
-
-- [ ] Test with storage limit reached
-- [ ] Test with malformed inputs in API requests
-- [ ] Test with special characters in filenames, descriptions, etc.
-- [ ] Test with concurrent uploads
-- [ ] Test with extremely large files
-- [ ] Test with slow network connections (simulate with throttling)
+#### 4.1 Download Page Functionality
+
+- [ ] **Access download page** at `/shared-file-download/`
+- [ ] **Direct file access** via `/shared-file-download/{fileid}`
+- [ ] **Download form testing**:
+  - [ ] Correct password and reference (should allow download)
+  - [ ] Incorrect password (should show error)
+  - [ ] Incorrect reference when required (should show error)
+  - [ ] Missing password (should show error)
+  - [ ] Empty form submission
+- [ ] **File status testing**:
+  - [ ] Active file (current time between active_from and active_to)
+  - [ ] Expired file (current time > active_to)
+  - [ ] Future file (current time < active_from)
+- [ ] **Download process**:
+  - [ ] Verify correct file headers are sent
+  - [ ] Verify original filename is preserved
+  - [ ] Verify download count increments
+  - [ ] Verify last download time updates
+- [ ] **Shortcode testing**:
+  - [ ] `[qdr_tss_download]` - generic form
+  - [ ] `[qdr_tss_download id="fileid"]` - specific file form
+
+#### 4.2 URL Rewriting and Pretty URLs
+
+- [ ] Test pretty URLs: `/shared-file-download/{fileid}`
+- [ ] Test legacy URLs with query parameters
+- [ ] Test URL redirection and rewrite rules
+- [ ] Verify 404 handling for invalid file IDs
+
+### 5. Scheduled Tasks and Cleanup Testing
+
+- [ ] **Manual cron trigger**:
+  - [ ] Use admin "Purge Expired Files" button
+  - [ ] Verify expired files are deleted from storage
+  - [ ] Verify expired records are deleted from database
+  - [ ] Verify non-expired files are not affected
+- [ ] **Automatic cron job**:
+  - [ ] Wait for hourly cron execution or trigger manually
+  - [ ] Test with files having different expiration dates
+  - [ ] Verify empty directories are cleaned up
+- [ ] **Storage management**:
+  - [ ] Test storage limit enforcement
+  - [ ] Verify storage usage calculations
+  - [ ] Test behavior when approaching storage limit
+
+### 6. Security Testing
+
+#### 6.1 File Access Security
+
+- [ ] **Direct file access attempts**:
+  - [ ] Try to access files directly via URL (should be blocked)
+  - [ ] Verify `.htaccess` rules are working
+  - [ ] Test directory browsing prevention
+- [ ] **Download security**:
+  - [ ] Password protection enforcement
+  - [ ] Reference code validation
+  - [ ] Time-based access control
+  - [ ] CSRF protection on download forms
+
+#### 6.2 API Security
+
+- [ ] **Authentication bypass attempts**:
+  - [ ] Missing API key header
+  - [ ] Invalid API key values
+  - [ ] API key in wrong header format
+- [ ] **Input validation**:
+  - [ ] SQL injection attempts in API parameters
+  - [ ] XSS attempts in file names and descriptions
+  - [ ] Path traversal attempts in file operations
+  - [ ] Large payload attacks
+- [ ] **AJAX Security**:
+  - [ ] Nonce verification for admin AJAX requests
+  - [ ] Permission checks for admin operations
+  - [ ] Rate limiting considerations
+
+### 7. Edge Cases and Error Handling
+
+#### 7.1 File System Edge Cases
+
+- [ ] **Storage scenarios**:
+  - [ ] Storage limit reached during upload
+  - [ ] Disk space full
+  - [ ] Permission issues on upload directory
+  - [ ] Corrupted chunk uploads
+- [ ] **Concurrent operations**:
+  - [ ] Multiple simultaneous uploads
+  - [ ] Upload while cleanup is running
+  - [ ] Edit file while someone is downloading
+
+#### 7.2 Database Edge Cases
+
+- [ ] **Data integrity**:
+  - [ ] Database connection failures during upload
+  - [ ] Orphaned files (in storage but not in database)
+  - [ ] Orphaned records (in database but no file)
+  - [ ] Character encoding issues in file names
+
+#### 7.3 Network and Performance
+
+- [ ] **Network issues**:
+  - [ ] Interrupted chunk uploads
+  - [ ] Slow network connections
+  - [ ] Timeout scenarios
+- [ ] **Performance**:
+  - [ ] Large file uploads (test with largest possible file)
+  - [ ] High number of files in storage
+  - [ ] Database query performance with many records
+
+### 8. Internationalization Testing
+
+- [ ] **Translation testing**:
+  - [ ] Switch WordPress language to Czech (cs_CZ)
+  - [ ] Verify admin interface displays Czech translations
+  - [ ] Test download page in Czech
+  - [ ] Verify JavaScript strings are translated
+- [ ] **Character encoding**:
+  - [ ] Upload files with non-ASCII names
+  - [ ] Test descriptions with special characters
+  - [ ] Test various character sets
 
 ## Automated Testing
 
-To implement automated testing for this plugin, you can use the WordPress testing framework which is based on PHPUnit.
-
 ### Setting Up the Test Environment
 
-1. **Install PHPUnit**:
+1. **Install PHPUnit and Dependencies**:
    ```bash
    composer require --dev phpunit/phpunit
+   composer require --dev brain/monkey
+   composer require --dev mockery/mockery
    ```
 
-2. **Install WordPress Test Library**:
+2. **WordPress Test Library Setup**:
    ```bash
    bash bin/install-wp-tests.sh wordpress_test root '' localhost latest
    ```
 
-3. **Create a PHPUnit Configuration File** (`phpunit.xml`):
+3. **PHPUnit Configuration** (`phpunit.xml`):
    ```xml
    <phpunit
        bootstrap="tests/bootstrap.php"
@@ -133,88 +283,55 @@ To implement automated testing for this plugin, you can use the WordPress testin
                <directory prefix="test-" suffix=".php">./tests/</directory>
            </testsuite>
        </testsuites>
+       <filter>
+           <whitelist>
+               <directory suffix=".php">./includes/</directory>
+               <directory suffix=".php">./admin/</directory>
+               <directory suffix=".php">./public/</directory>
+               <directory suffix=".php">./rest-api/</directory>
+           </whitelist>
+       </filter>
    </phpunit>
    ```
 
-4. **Create Bootstrap File** (`tests/bootstrap.php`):
-   ```php
-   <?php
-   /**
-    * PHPUnit bootstrap file
-    */
-   
-   // Require composer dependencies
-   require_once dirname(dirname(__FILE__)) . '/vendor/autoload.php';
-   
-   // Load the WordPress test suite
-   $_tests_dir = getenv('WP_TESTS_DIR');
-   if (!$_tests_dir) {
-       $_tests_dir = '/tmp/wordpress-tests-lib';
-   }
-   
-   // Load WordPress test framework
-   require_once $_tests_dir . '/includes/functions.php';
-   
-   /**
-    * Load the plugin being tested
-    */
-   function _manually_load_plugin() {
-       require dirname(dirname(__FILE__)) . '/qdr-temporary-shared-storage.php';
-   }
-   tests_add_filter('muplugins_loaded', '_manually_load_plugin');
-   
-   // Start up the WordPress test environment
-   require $_tests_dir . '/includes/bootstrap.php';
-   ```
-
-### Writing Test Cases
-
-#### Database Manager Tests
+### Unit Test Examples
 
-Create a file `tests/test-db-manager.php`:
+#### Database Manager Tests (`tests/test-db-manager.php`)
 
 ```php
 <?php
-/**
- * Class TestDbManager
- *
- * @package QDR_Temporary_Shared_Storage
- */
-
 class TestDbManager extends WP_UnitTestCase {
-    /**
-     * Setup function runs before each test
-     */
-    public function setUp() {
+    private $db_manager;
+
+    public function setUp(): void {
         parent::setUp();
         $this->db_manager = QDR_TSS_DB_Manager::instance();
     }
 
-    /**
-     * Test creating the database table
-     */
     public function test_create_table() {
         global $wpdb;
         
-        // Delete table if it exists
         $table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE;
         $wpdb->query("DROP TABLE IF EXISTS $table_name");
         
-        // Create table
         $result = $this->db_manager->create_table();
         
-        // Check if table exists
         $table_exists = $wpdb->get_var("SHOW TABLES LIKE '$table_name'") === $table_name;
-        
         $this->assertTrue($table_exists);
+        
+        // Test table structure
+        $columns = $wpdb->get_results("DESCRIBE $table_name");
+        $column_names = wp_list_pluck($columns, 'Field');
+        
+        $this->assertContains('fileid', $column_names);
+        $this->assertContains('original_file_name', $column_names);
+        $this->assertContains('active_from', $column_names);
+        $this->assertContains('active_to', $column_names);
     }
 
-    /**
-     * Test inserting an item
-     */
-    public function test_insert_item() {
+    public function test_insert_and_get_item() {
         $data = array(
-            'media_id' => 123456789,
+            'fileid' => 'test123456789',
             'original_file_name' => 'test.pdf',
             'description' => 'Test description',
             'message' => 'Test message',
@@ -227,46 +344,59 @@ class TestDbManager extends WP_UnitTestCase {
         );
         
         $item_id = $this->db_manager->insert_item($data);
-        
         $this->assertNotFalse($item_id);
         $this->assertGreaterThan(0, $item_id);
         
-        // Get the item and verify data
+        // Test get by ID
         $item = $this->db_manager->get_item($item_id);
-        
-        $this->assertEquals($data['media_id'], $item->media_id);
+        $this->assertEquals($data['fileid'], $item->fileid);
         $this->assertEquals($data['original_file_name'], $item->original_file_name);
-        $this->assertEquals($data['description'], $item->description);
+        
+        // Test get by fileid
+        $item_by_fileid = $this->db_manager->get_item_by_fileid($data['fileid']);
+        $this->assertEquals($item_id, $item_by_fileid->id);
     }
 
-    // Additional tests for update, delete, get operations...
+    public function test_get_items_with_pagination() {
+        // Insert test data
+        for ($i = 1; $i <= 15; $i++) {
+            $this->db_manager->insert_item([
+                'fileid' => 'test' . $i,
+                'original_file_name' => "test{$i}.pdf",
+                'description' => "Test file {$i}",
+                'active_from' => current_time('mysql'),
+                'active_to' => date('Y-m-d H:i:s', strtotime('+30 days')),
+                'password' => wp_hash_password('test'),
+                'file_path' => "test/test{$i}.pdf",
+                'file_size' => 1024 * $i
+            ]);
+        }
+        
+        // Test pagination
+        $result = $this->db_manager->get_items(['per_page' => 10, 'page' => 1]);
+        $this->assertEquals(10, count($result['items']));
+        $this->assertEquals(15, $result['total']);
+        $this->assertEquals(2, $result['total_pages']);
+        
+        // Test second page
+        $result = $this->db_manager->get_items(['per_page' => 10, 'page' => 2]);
+        $this->assertEquals(5, count($result['items']));
+    }
 }
 ```
 
-#### File Manager Tests
-
-Create a file `tests/test-file-manager.php`:
+#### File Manager Tests (`tests/test-file-manager.php`)
 
 ```php
 <?php
-/**
- * Class TestFileManager
- *
- * @package QDR_Temporary_Shared_Storage
- */
-
 class TestFileManager extends WP_UnitTestCase {
-    /**
-     * Setup function runs before each test
-     */
-    public function setUp() {
+    private $file_manager;
+
+    public function setUp(): void {
         parent::setUp();
         $this->file_manager = QDR_TSS_File_Manager::instance();
     }
 
-    /**
-     * Test initializing the file system
-     */
     public function test_init_file_system() {
         $result = $this->file_manager->init_file_system();
         
@@ -276,37 +406,60 @@ class TestFileManager extends WP_UnitTestCase {
         $this->assertTrue(file_exists(QDR_TSS_UPLOADS_DIR . '.htaccess'));
     }
 
-    /**
-     * Test creating a dated directory
-     */
-    public function test_create_dated_directory() {
-        $date = date('Y/m/d');
-        $dir = $this->file_manager->create_dated_directory($date);
+    public function test_generate_fileid() {
+        $fileid1 = $this->file_manager->generate_fileid();
+        $fileid2 = $this->file_manager->generate_fileid();
         
-        $this->assertTrue(file_exists($dir));
+        $this->assertNotEquals($fileid1, $fileid2);
+        $this->assertTrue(strlen($fileid1) > 40);
+        $this->assertMatchesRegularExpression('/^[a-zA-Z0-9]+$/', $fileid1);
     }
 
-    // Additional tests for chunked uploads, file deletion, etc...
+    public function test_chunked_upload_process() {
+        // Initialize upload
+        $upload_id = $this->file_manager->init_chunked_upload();
+        $this->assertNotFalse($upload_id);
+        
+        // Create test chunk data
+        $chunk_data = 'This is test chunk data';
+        $temp_file = tempnam(sys_get_temp_dir(), 'test_chunk');
+        file_put_contents($temp_file, $chunk_data);
+        
+        $file_array = [
+            'tmp_name' => $temp_file,
+            'name' => 'test_chunk',
+            'size' => strlen($chunk_data)
+        ];
+        
+        // Store chunk
+        $result = $this->file_manager->store_chunk($upload_id, 0, $file_array);
+        $this->assertTrue($result);
+        
+        // Check all chunks (should return true for single chunk)
+        $all_chunks = $this->file_manager->check_all_chunks($upload_id, 1);
+        $this->assertTrue($all_chunks);
+        
+        // Finalize upload
+        $final_result = $this->file_manager->finalize_chunked_upload($upload_id, 1, 'test.txt');
+        $this->assertIsArray($final_result);
+        $this->assertArrayHasKey('file_path', $final_result);
+        $this->assertArrayHasKey('file_size', $final_result);
+        
+        // Cleanup
+        unlink($temp_file);
+    }
 }
 ```
 
-#### REST API Tests
-
-Create a file `tests/test-rest-api.php`:
+#### REST API Tests (`tests/test-rest-api.php`)
 
 ```php
 <?php
-/**
- * Class TestRestApi
- *
- * @package QDR_Temporary_Shared_Storage
- */
-
 class TestRestApi extends WP_UnitTestCase {
-    /**
-     * Setup function runs before each test
-     */
-    public function setUp() {
+    private $server;
+    private $api_key;
+
+    public function setUp(): void {
         parent::setUp();
         global $wp_rest_server;
         $this->server = $wp_rest_server = new WP_REST_Server;
@@ -318,9 +471,6 @@ class TestRestApi extends WP_UnitTestCase {
         $settings->update_setting('api_key', $this->api_key);
     }
 
-    /**
-     * Test unauthorized access
-     */
     public function test_unauthorized_access() {
         $request = new WP_REST_Request('GET', '/qdr-tss/v1/media');
         $response = $this->server->dispatch($request);
@@ -328,9 +478,6 @@ class TestRestApi extends WP_UnitTestCase {
         $this->assertEquals(403, $response->get_status());
     }
 
-    /**
-     * Test authorized access
-     */
     public function test_authorized_access() {
         $request = new WP_REST_Request('GET', '/qdr-tss/v1/media');
         $request->add_header('X-Api-Key', $this->api_key);
@@ -339,158 +486,262 @@ class TestRestApi extends WP_UnitTestCase {
         $this->assertEquals(200, $response->get_status());
     }
 
-    // Additional tests for other API endpoints...
+    public function test_upload_init() {
+        $request = new WP_REST_Request('POST', '/qdr-tss/v1/upload/init');
+        $request->add_header('X-Api-Key', $this->api_key);
+        $request->set_param('original_file_name', 'test.pdf');
+        $request->set_param('file_size', 1024);
+        
+        $response = $this->server->dispatch($request);
+        
+        $this->assertEquals(200, $response->get_status());
+        
+        $data = $response->get_data();
+        $this->assertArrayHasKey('upload_id', $data);
+        $this->assertArrayHasKey('status', $data);
+        $this->assertEquals('initialized', $data['status']);
+    }
 }
 ```
 
-### Running the Tests
-
-Run the tests using the PHPUnit command:
-
-```bash
-./vendor/bin/phpunit
-```
-
-## Integration Testing with Client Applications
+### Integration Testing Scripts
 
-To test integration with client applications, you can create simple client scripts in PHP, JavaScript, or other languages that communicate with your WordPress site's REST API.
-
-### Example PHP Client
+#### API Client Test Script (`tests/integration/api-client-test.php`)
 
 ```php
 <?php
-// Define API credentials
-$api_url = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
-$api_key = 'your-api-key-here';
+/**
+ * Integration test script for API client functionality
+ */
 
-// Test the API
-function test_api() {
-    global $api_url, $api_key;
+class QDR_TSS_Integration_Test {
+    private $api_url;
+    private $api_key;
     
-    // Test initialization
-    $init_response = initialize_upload('test.txt', 1024);
-    if (!isset($init_response['upload_id'])) {
-        echo "FAIL: Initialize upload\n";
-        return;
+    public function __construct($api_url, $api_key) {
+        $this->api_url = rtrim($api_url, '/');
+        $this->api_key = $api_key;
     }
-    echo "PASS: Initialize upload\n";
     
-    // Test chunk upload
-    $upload_id = $init_response['upload_id'];
-    $chunk_response = upload_chunk($upload_id, 0, 1, 'Test content');
-    if (!isset($chunk_response['status']) || $chunk_response['status'] !== 'chunk_uploaded') {
-        echo "FAIL: Upload chunk\n";
-        return;
+    public function run_all_tests() {
+        echo "Starting QDR TSS API Integration Tests\n";
+        echo "=====================================\n\n";
+        
+        try {
+            $this->test_complete_upload_workflow();
+            $this->test_file_management();
+            $this->test_error_handling();
+            
+            echo "\n✅ All tests passed!\n";
+        } catch (Exception $e) {
+            echo "\n❌ Test failed: " . $e->getMessage() . "\n";
+        }
     }
-    echo "PASS: Upload chunk\n";
     
-    // Test finalize upload
-    $finalize_response = finalize_upload($upload_id, 1, 'test.txt', 'Test description', 'test_password');
-    if (!isset($finalize_response['media_id'])) {
-        echo "FAIL: Finalize upload\n";
-        return;
+    private function test_complete_upload_workflow() {
+        echo "Testing complete upload workflow...\n";
+        
+        // Create test file
+        $test_content = "This is a test file for QDR TSS integration testing.";
+        $temp_file = tempnam(sys_get_temp_dir(), 'qdr_test');
+        file_put_contents($temp_file, $test_content);
+        
+        try {
+            $result = $this->upload_file($temp_file, 'Integration Test File', 'test_password', 'INT_TEST_001');
+            
+            echo "  ✓ File uploaded successfully\n";
+            echo "  ✓ File ID: {$result['fileid']}\n";
+            echo "  ✓ Permalink: {$result['permalink']}\n";
+            
+            // Store fileid for later tests
+            $this->test_fileid = $result['fileid'];
+            
+        } finally {
+            unlink($temp_file);
+        }
     }
-    echo "PASS: Finalize upload\n";
     
-    // Test get media
-    $media_id = $finalize_response['media_id'];
-    $get_response = get_media($media_id);
-    if (!isset($get_response['media_id']) || $get_response['media_id'] != $media_id) {
-        echo "FAIL: Get media\n";
-        return;
+    private function test_file_management() {
+        echo "Testing file management operations...\n";
+        
+        if (!isset($this->test_fileid)) {
+            throw new Exception("No test file ID available");
+        }
+        
+        // Test get file details
+        $details = $this->get_file_details($this->test_fileid);
+        echo "  ✓ Retrieved file details\n";
+        
+        // Test update file
+        $updated = $this->update_file($this->test_fileid, [
+            'message' => 'Updated via integration test'
+        ]);
+        echo "  ✓ Updated file properties\n";
+        
+        // Test delete file
+        $this->delete_file($this->test_fileid);
+        echo "  ✓ Deleted file\n";
     }
-    echo "PASS: Get media\n";
     
-    // Test update media
-    $update_response = update_media($media_id, ['message' => 'Updated message']);
-    if (!isset($update_response['message']) || $update_response['message'] !== 'Updated message') {
-        echo "FAIL: Update media\n";
-        return;
+    private function test_error_handling() {
+        echo "Testing error handling...\n";
+        
+        // Test with invalid API key
+        $old_key = $this->api_key;
+        $this->api_key = 'invalid_key';
+        
+        try {
+            $this->get_file_list();
+            throw new Exception("Should have failed with invalid API key");
+        } catch (Exception $e) {
+            if (strpos($e->getMessage(), '403') !== false) {
+                echo "  ✓ Invalid API key properly rejected\n";
+            } else {
+                throw $e;
+            }
+        }
+        
+        $this->api_key = $old_key;
+        
+        // Test with non-existent file
+        try {
+            $this->get_file_details('nonexistent123');
+            throw new Exception("Should have failed with non-existent file");
+        } catch (Exception $e) {
+            if (strpos($e->getMessage(), '404') !== false) {
+                echo "  ✓ Non-existent file properly handled\n";
+            } else {
+                throw $e;
+            }
+        }
     }
-    echo "PASS: Update media\n";
     
-    // Test delete media
-    $delete_response = delete_media($media_id);
-    if (!isset($delete_response['deleted']) || $delete_response['deleted'] !== true) {
-        echo "FAIL: Delete media\n";
-        return;
+    // API helper methods...
+    private function upload_file($file_path, $description, $password, $reference) {
+        // Implementation similar to previous examples
+        // ... (chunked upload implementation)
     }
-    echo "PASS: Delete media\n";
     
-    echo "All tests passed!\n";
-}
-
-// API helper functions
-function initialize_upload($filename, $filesize) {
-    global $api_url, $api_key;
+    private function get_file_details($fileid) {
+        // ... (implementation)
+    }
     
-    $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' => $filename,
-        'file_size' => $filesize
-    ]));
+    private function update_file($fileid, $data) {
+        // ... (implementation)
+    }
     
-    $response = curl_exec($ch);
-    curl_close($ch);
+    private function delete_file($fileid) {
+        // ... (implementation)
+    }
     
-    return json_decode($response, true);
+    private function get_file_list() {
+        // ... (implementation)
+    }
 }
 
-// Implement other API helper functions...
-
 // Run tests
-test_api();
+$tester = new QDR_TSS_Integration_Test(
+    'https://your-test-site.com/wp-json/qdr-tss/v1',
+    'your-test-api-key'
+);
+
+$tester->run_all_tests();
 ```
 
 ## Performance Testing
 
-To test the performance of the plugin, especially with large files and under high load, you can use tools like Apache JMeter or a simple script to simulate multiple concurrent uploads.
+### Load Testing Script
+
+```bash
+#!/bin/bash
+# Simple load test for file uploads
+
+API_URL="https://your-test-site.com/wp-json/qdr-tss/v1"
+API_KEY="your-test-api-key"
+CONCURRENT_UPLOADS=5
+TEST_FILE_SIZE=1048576  # 1MB
+
+echo "Starting load test with $CONCURRENT_UPLOADS concurrent uploads"
+
+for i in $(seq 1 $CONCURRENT_UPLOADS); do
+    (
+        # Create test file
+        dd if=/dev/zero of="test_file_$i.bin" bs=$TEST_FILE_SIZE count=1 2>/dev/null
+        
+        # Upload file
+        echo "Starting upload $i"
+        start_time=$(date +%s)
+        
+        # Your upload implementation here
+        # ...
+        
+        end_time=$(date +%s)
+        duration=$((end_time - start_time))
+        
+        echo "Upload $i completed in ${duration}s"
+        
+        # Cleanup
+        rm "test_file_$i.bin"
+    ) &
+done
+
+wait
+echo "All uploads completed"
+```
+
+## Browser Testing Checklist
+
+### Admin Interface Browser Testing
 
-### Test Scenarios
+Test the admin interface across different browsers:
 
-1. **Large File Upload**: Test uploading a very large file (1GB+) and measure:
-   - Total upload time
-   - Memory usage
-   - CPU usage
+- [ ] **Chrome (latest)**
+- [ ] **Firefox (latest)**
+- [ ] **Safari (latest)**
+- [ ] **Edge (latest)**
+- [ ] **Mobile Chrome**
+- [ ] **Mobile Safari**
 
-2. **Concurrent Uploads**: Test multiple clients uploading files simultaneously and measure:
-   - Success rate
-   - Average upload time
-   - Server resource usage
+For each browser, test:
+- [ ] Media page layout and functionality
+- [ ] Settings page functionality
+- [ ] Modal dialogs
+- [ ] Action buttons (Copy Permalink, Force Activate)
+- [ ] AJAX operations
+- [ ] JavaScript error console (should be clean)
 
-3. **Storage Limit Testing**: Test behavior when approaching and exceeding storage limits.
+## Final Testing Checklist
 
-## Security Testing
+Before deploying to production:
 
-### Checklist
+- [ ] All manual tests pass
+- [ ] Unit tests pass (`./vendor/bin/phpunit`)
+- [ ] Integration tests pass
+- [ ] Security tests pass
+- [ ] Performance is acceptable
+- [ ] Documentation is updated
+- [ ] Translation files are current
+- [ ] Browser compatibility verified
+- [ ] Mobile responsiveness tested
+- [ ] Error logging reviewed
+- [ ] Backup and restore procedures tested
 
-1. **API Authentication**:
-   - [ ] Test API requests with missing API key
-   - [ ] Test API requests with invalid API key
-   - [ ] Test API requests with valid API key
+## Troubleshooting Common Issues
 
-2. **File Access**:
-   - [ ] Verify direct access to uploaded files is blocked
-   - [ ] Verify download requires valid password
-   - [ ] Verify download respects active dates
+### Database Issues
+- **Problem**: Table not created during activation
+- **Solution**: Check database user permissions, verify WordPress table prefix
 
-3. **Input Validation**:
-   - [ ] Test with malformed JSON requests
-   - [ ] Test with malicious file uploads
-   - [ ] Test SQL injection attempts
-   - [ ] Test XSS attempts in file names and descriptions
+### File Upload Issues
+- **Problem**: Chunks not uploading
+- **Solution**: Check file permissions, PHP memory limits, server timeout settings
 
-4. **File Operations**:
-   - [ ] Test upload paths for directory traversal
-   - [ ] Test file type restrictions
-   - [ ] Test file size limits
+### API Issues
+- **Problem**: 403 errors with valid API key
+- **Solution**: Verify header format, check for mod_security rules, test with different HTTP clients
 
-## Conclusion
+### Download Issues
+- **Problem**: Files not downloading
+- **Solution**: Check file permissions, verify .htaccess rules, test rewrite rules
 
-This comprehensive testing approach will help ensure that the refactored QDR Temporary Shared Storage plugin works correctly across all components and integrates properly with WordPress and client applications. By following this guide, you can identify and fix any issues before deploying the plugin to production environments.
+This comprehensive testing approach ensures the plugin works reliably across all components and integrates properly with WordPress and client applications.

+ 10 - 1
qdr-temporary-shared-storage/admin/class-qdr-tss-admin.php

@@ -102,8 +102,11 @@ class QDR_TSS_Admin {
                 'view_details' => __('View Details', 'qdr-temporary-shared-storage'),
                 'edit' => __('Edit', 'qdr-temporary-shared-storage'),
                 'delete' => __('Delete', 'qdr-temporary-shared-storage'),
+                'copy_permalink' => __('Copy Permalink', 'qdr-temporary-shared-storage'),
+                'force_activate' => __('Force Activate', 'qdr-temporary-shared-storage'),
                 'confirm_delete' => __('Are you sure you want to delete this file?', 'qdr-temporary-shared-storage'),
                 'confirm_purge' => __('Are you sure you want to purge all expired files?', 'qdr-temporary-shared-storage'),
+                'confirm_force_activate' => __('Are you sure you want to force activate this file? This will reset the active period starting from now.', 'qdr-temporary-shared-storage'),
                 'error_loading_items' => __('Error loading items.', 'qdr-temporary-shared-storage'),
                 'error_loading_items_try_again' => __('Error loading items. Please try again.', 'qdr-temporary-shared-storage'),
                 'error_loading_item_details' => __('Error loading item details.', 'qdr-temporary-shared-storage'),
@@ -116,6 +119,12 @@ class QDR_TSS_Admin {
                 'error_deleting_file_try_again' => __('Error deleting file. Please try again.', 'qdr-temporary-shared-storage'),
                 'shortcode_copied' => __('Shortcode copied to clipboard.', 'qdr-temporary-shared-storage'),
                 'permalink_copied' => __('Permalink copied to clipboard.', 'qdr-temporary-shared-storage'),
+                'permalink_copied_to_clipboard' => __('Permalink copied to clipboard successfully.', 'qdr-temporary-shared-storage'),
+                'error_getting_permalink' => __('Error getting permalink.', 'qdr-temporary-shared-storage'),
+                'error_getting_permalink_try_again' => __('Error getting permalink. Please try again.', 'qdr-temporary-shared-storage'),
+                'item_force_activated' => __('Item has been force activated successfully.', 'qdr-temporary-shared-storage'),
+                'error_force_activating' => __('Error force activating item.', 'qdr-temporary-shared-storage'),
+                'error_force_activating_try_again' => __('Error force activating item. Please try again.', 'qdr-temporary-shared-storage'),
             )
         ));
     }
@@ -164,4 +173,4 @@ class QDR_TSS_Admin {
     public function render_settings_page() {
         require_once plugin_dir_path(dirname(__FILE__)) . 'admin/partials/settings-page.php';
     }
-}
+}

+ 58 - 2
qdr-temporary-shared-storage/admin/class-qdr-tss-media-page.php

@@ -194,7 +194,7 @@ class QDR_TSS_Media_Page {
      *
      * @since    1.0.0
      */
-   public function ajax_get_item_details() {
+    public function ajax_get_item_details() {
         // Check nonce
         if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'qdr_tss_nonce')) {
             wp_send_json_error(array('message' => __('Security check failed.', 'qdr-temporary-shared-storage')));
@@ -224,4 +224,60 @@ class QDR_TSS_Media_Page {
             'item' => $item
         ));
     }
-}
+
+    /**
+     * AJAX handler for force activating an item.
+     *
+     * @since    1.0.0
+     */
+    public function ajax_force_activate_item() {
+        // Check nonce
+        if (!isset($_POST['nonce']) || !wp_verify_nonce($_POST['nonce'], 'qdr_tss_nonce')) {
+            wp_send_json_error(array('message' => __('Security check failed.', 'qdr-temporary-shared-storage')));
+        }
+        
+        // Check required parameters
+        if (!isset($_POST['id']) || empty($_POST['id'])) {
+            wp_send_json_error(array('message' => __('Missing item ID.', 'qdr-temporary-shared-storage')));
+        }
+        
+        $id = intval($_POST['id']);
+        
+        // Get the item
+        $item = $this->db_manager->get_item($id);
+        if (!$item) {
+            wp_send_json_error(array('message' => __('Item not found.', 'qdr-temporary-shared-storage')));
+        }
+        
+        // Calculate new dates using absolute difference formula
+        $now = current_time('mysql');
+        $active_from = new DateTime($item->active_from);
+        $active_to = new DateTime($item->active_to);
+        
+        // Calculate absolute difference in seconds
+        $duration_seconds = abs($active_to->getTimestamp() - $active_from->getTimestamp());
+        
+        // Set new active period starting from now
+        $new_active_from = new DateTime($now);
+        $new_active_to = clone $new_active_from;
+        $new_active_to->add(new DateInterval('PT' . $duration_seconds . 'S'));
+        
+        // Update in database
+        $data = array(
+            'active_from' => $new_active_from->format('Y-m-d H:i:s'),
+            'active_to' => $new_active_to->format('Y-m-d H:i:s')
+        );
+        
+        $result = $this->db_manager->update_item($id, $data);
+        
+        if ($result === false) {
+            wp_send_json_error(array('message' => __('Failed to force activate item.', 'qdr-temporary-shared-storage')));
+        }
+        
+        wp_send_json_success(array(
+            'message' => __('Item has been force activated successfully.', 'qdr-temporary-shared-storage'),
+            'new_active_from' => $new_active_from->format('Y-m-d H:i:s'),
+            'new_active_to' => $new_active_to->format('Y-m-d H:i:s')
+        ));
+    }
+}

+ 62 - 6
qdr-temporary-shared-storage/admin/css/admin.css

@@ -60,7 +60,7 @@
 }
 
 .qdr-tss-table .column-actions {
-    width: 120px;
+    width: 160px; /* Increased width to accommodate new buttons */
     text-align: center;
 }
 
@@ -209,17 +209,55 @@
 .qdr-tss-action-buttons {
     display: flex;
     justify-content: center;
+    flex-wrap: wrap;
+    gap: 2px;
 }
 
 .qdr-tss-action-buttons .button {
-    margin: 0 2px;
-    padding: 0 5px;
-    height: 30px;
+    margin: 0 1px;
+    padding: 0 4px;
+    height: 28px;
+    min-width: 28px;
+    font-size: 12px;
 }
 
 .qdr-tss-action-buttons .dashicons {
-    font-size: 16px;
-    line-height: 30px;
+    font-size: 14px;
+    line-height: 28px;
+}
+
+/* Specific button colors for better UX */
+.qdr-tss-copy-permalink-button {
+    background-color: #007cba;
+    color: #fff;
+    border-color: #007cba;
+}
+
+.qdr-tss-copy-permalink-button:hover {
+    background-color: #005a87;
+    border-color: #005a87;
+}
+
+.qdr-tss-force-activate-button {
+    background-color: #00a32a;
+    color: #fff;
+    border-color: #00a32a;
+}
+
+.qdr-tss-force-activate-button:hover {
+    background-color: #008a20;
+    border-color: #008a20;
+}
+
+.qdr-tss-delete-button {
+    background-color: #d63638;
+    color: #fff;
+    border-color: #d63638;
+}
+
+.qdr-tss-delete-button:hover {
+    background-color: #b32d2e;
+    border-color: #b32d2e;
 }
 
 /* Details */
@@ -235,3 +273,21 @@
     width: 30%;
     text-align: left;
 }
+
+/* Responsive adjustments for action buttons */
+@media screen and (max-width: 782px) {
+    .qdr-tss-action-buttons {
+        flex-direction: column;
+        gap: 1px;
+    }
+    
+    .qdr-tss-action-buttons .button {
+        width: 100%;
+        margin: 1px 0;
+        min-width: auto;
+    }
+    
+    .qdr-tss-table .column-actions {
+        width: 120px;
+    }
+}

+ 133 - 3
qdr-temporary-shared-storage/admin/js/admin.js

@@ -4,6 +4,8 @@
  * Handles all admin-side functionality including:
  * - Media page (loading, pagination, sorting, filtering, editing, deleting)
  * - Settings page (API key generation, settings saving, purging expired files)
+ * - New actions: Copy permalink and Force Activate
+ * - Date validation for active period
  *
  * @package    QDR_Temporary_Shared_Storage
  * @since      1.0.0
@@ -108,7 +110,16 @@
             // Handle edit item form submission
             $('#qdr-tss-edit-form').on('submit', function(e) {
                 e.preventDefault();
-                self.updateItem();
+                
+                // Validate dates before submitting
+                if (self.validateActivePeriod()) {
+                    self.updateItem();
+                }
+            });
+            
+            // Real-time date validation
+            $('#qdr-tss-edit-active-from, #qdr-tss-edit-active-to').on('change blur', function() {
+                self.validateActivePeriod();
             });
             
             // Handle confirm delete
@@ -118,15 +129,64 @@
             });
         },
 
+        /**
+         * Validate active period dates
+         */
+        validateActivePeriod: function() {
+            var activeFrom = $('#qdr-tss-edit-active-from').val();
+            var activeTo = $('#qdr-tss-edit-active-to').val();
+            var errorContainer = $('#qdr-tss-date-validation-error');
+            
+            // Remove existing error container
+            errorContainer.remove();
+            
+            // If either field is empty, no validation needed yet
+            if (!activeFrom || !activeTo) {
+                return true;
+            }
+            
+            var fromDate = new Date(activeFrom);
+            var toDate = new Date(activeTo);
+            
+            // Check if dates are valid
+            if (isNaN(fromDate.getTime()) || isNaN(toDate.getTime())) {
+                this.showDateValidationError(qdr_tss.strings.invalid_date_format || 'Invalid date format');
+                return false;
+            }
+            
+            // Check if active from is greater than or equal to active to
+            if (fromDate >= toDate) {
+                this.showDateValidationError(qdr_tss.strings.active_from_must_be_before_active_to || 'Active From date must be before Active To date');
+                return false;
+            }
+            
+            return true;
+        },
+
+        /**
+         * Show date validation error
+         */
+        showDateValidationError: function(message) {
+            var errorHtml = '<div id="qdr-tss-date-validation-error" class="notice notice-error inline" style="margin: 10px 0;"><p>' + message + '</p></div>';
+            $('#qdr-tss-edit-active-to').closest('.qdr-tss-form-row').after(errorHtml);
+        },
+
         /**
          * Initialize datepicker
          */
         initDatepicker: function() {
             $(document).on('focus', '.qdr-tss-datepicker', function() {
+                var self = QDR_TSS_Media;
                 $(this).datepicker({
                     dateFormat: 'yy-mm-dd',
                     changeMonth: true,
-                    changeYear: true
+                    changeYear: true,
+                    onSelect: function() {
+                        // Trigger validation when date is selected
+                        setTimeout(function() {
+                            self.validateActivePeriod();
+                        }, 100);
+                    }
                 });
             });
         },
@@ -155,6 +215,18 @@
                 self.showDeleteConfirmation(id);
             });
             
+            // Copy permalink button (NEW)
+            $('.qdr-tss-copy-permalink-button').off('click').on('click', function() {
+                var id = $(this).data('id');
+                self.copyPermalinkToClipboard(id);
+            });
+            
+            // Force activate button (NEW)
+            $('.qdr-tss-force-activate-button').off('click').on('click', function() {
+                var id = $(this).data('id');
+                self.forceActivateItem(id);
+            });
+            
             // Pagination
             $('.qdr-tss-page-button').off('click').on('click', function() {
                 self.currentPage = parseInt($(this).data('page'));
@@ -226,6 +298,8 @@
                 row += '<div class="qdr-tss-action-buttons">';
                 row += '<button class="button qdr-tss-view-button" data-id="' + item.id + '" title="' + qdr_tss.strings.view_details + '"><span class="dashicons dashicons-visibility"></span></button>';
                 row += '<button class="button qdr-tss-edit-button" data-id="' + item.id + '" title="' + qdr_tss.strings.edit + '"><span class="dashicons dashicons-edit"></span></button>';
+                row += '<button class="button qdr-tss-copy-permalink-button" data-id="' + item.id + '" title="' + qdr_tss.strings.copy_permalink + '"><span class="dashicons dashicons-admin-links"></span></button>';
+                row += '<button class="button qdr-tss-force-activate-button" data-id="' + item.id + '" title="' + qdr_tss.strings.force_activate + '"><span class="dashicons dashicons-clock"></span></button>';
                 row += '<button class="button qdr-tss-delete-button" data-id="' + item.id + '" title="' + qdr_tss.strings.delete + '"><span class="dashicons dashicons-trash"></span></button>';
                 row += '</div>';
                 row += '</td>';
@@ -242,6 +316,59 @@
             this.updateSortIndicators();
         },
 
+        /**
+         * Copy permalink to clipboard (NEW FUNCTION)
+         */
+        copyPermalinkToClipboard: function(id) {
+            var self = this;
+            var data = {
+                action: 'qdr_tss_get_item_details',
+                nonce: qdr_tss.nonce,
+                id: id
+            };
+            
+            $.post(ajaxurl, data, function(response) {
+                if (response.success) {
+                    var permalink = response.data.item.permalink;
+                    self.copyToClipboard(permalink);
+                    alert(qdr_tss.strings.permalink_copied_to_clipboard);
+                } else {
+                    alert(response.data.message || qdr_tss.strings.error_getting_permalink);
+                }
+            }).fail(function() {
+                alert(qdr_tss.strings.error_getting_permalink_try_again);
+            });
+        },
+
+        /**
+         * Force activate item (NEW FUNCTION)
+         */
+        forceActivateItem: function(id) {
+            var self = this;
+            
+            if (!confirm(qdr_tss.strings.confirm_force_activate)) {
+                return;
+            }
+            
+            var data = {
+                action: 'qdr_tss_force_activate_item',
+                nonce: qdr_tss.nonce,
+                id: id
+            };
+            
+            $.post(ajaxurl, data, function(response) {
+                if (response.success) {
+                    alert(response.data.message || qdr_tss.strings.item_force_activated);
+                    // Reload items to show updated dates
+                    self.loadItems();
+                } else {
+                    alert(response.data.message || qdr_tss.strings.error_force_activating);
+                }
+            }).fail(function() {
+                alert(qdr_tss.strings.error_force_activating_try_again);
+            });
+        },
+
         /**
          * Get item status class
          */
@@ -409,6 +536,9 @@
             // Clear status
             $('#qdr-tss-edit-status').html('');
             
+            // Remove any existing validation errors
+            $('#qdr-tss-date-validation-error').remove();
+            
             // Show modal
             $('#qdr-tss-edit-modal').show();
         },
@@ -659,4 +789,4 @@
         }
     });
 
-})(jQuery);
+})(jQuery);

+ 3 - 1
qdr-temporary-shared-storage/includes/class-qdr-tss.php

@@ -178,7 +178,7 @@ class QDR_TSS {
      * @since    1.0.0
      * @access   private
      */
-    private function define_admin_hooks() {
+     private function define_admin_hooks() {
         $plugin_admin = new QDR_TSS_Admin($this->get_plugin_name(), $this->get_version());
         $media_page = new QDR_TSS_Media_Page($this->get_plugin_name(), $this->get_version());
         $settings_page = new QDR_TSS_Settings_Page($this->get_plugin_name(), $this->get_version());
@@ -192,6 +192,8 @@ class QDR_TSS {
         $this->loader->add_action('wp_ajax_qdr_tss_get_items', $media_page, 'ajax_get_items');
         $this->loader->add_action('wp_ajax_qdr_tss_edit_item', $media_page, 'ajax_edit_item');
         $this->loader->add_action('wp_ajax_qdr_tss_delete_item', $media_page, 'ajax_delete_item');
+        $this->loader->add_action('wp_ajax_qdr_tss_get_item_details', $media_page, 'ajax_get_item_details');
+        $this->loader->add_action('wp_ajax_qdr_tss_force_activate_item', $media_page, 'ajax_force_activate_item'); // NEW
         $this->loader->add_action('wp_ajax_qdr_tss_save_settings', $settings_page, 'ajax_save_settings');
         $this->loader->add_action('wp_ajax_qdr_tss_purge_expired', $settings_page, 'ajax_purge_expired');
     }

BIN
qdr-temporary-shared-storage/languages/qdr-temporary-shared-storage-cs_CZ.mo


+ 120 - 21
qdr-temporary-shared-storage/languages/qdr-temporary-shared-storage-cs_CZ.po

@@ -6,7 +6,7 @@ msgstr ""
 "Report-Msgid-Bugs-To: https://wordpress.org/support/plugin/qdr-temporary-"
 "shared-storage\n"
 "POT-Creation-Date: 2025-05-20T12:00:00+00:00\n"
-"PO-Revision-Date: 2025-05-20 21:38+0200\n"
+"PO-Revision-Date: 2025-05-25 21:38+0200\n"
 "Last-Translator: Dalibor Votruba <dvotruba@quadarax.com>\n"
 "Language-Team: \n"
 "Language: cs_CZ\n"
@@ -91,66 +91,113 @@ msgstr "Zobrazit detaily"
 msgid "Edit"
 msgstr "Upravit"
 
-#: admin/class-qdr-tss-admin.php:87 admin/partials/media-page.php:194
+#: admin/class-qdr-tss-admin.php:87
+msgctxt "Admin action button"
 msgid "Delete"
 msgstr "Smazat"
 
 #: admin/class-qdr-tss-admin.php:88
+msgctxt "Admin action button"
+msgid "Copy Permalink"
+msgstr "Kopírovat permalink"
+
+#: admin/class-qdr-tss-admin.php:89
+msgid "Force Activate"
+msgstr "Vynutit aktivaci"
+
+#: admin/class-qdr-tss-admin.php:90
 msgid "Are you sure you want to delete this file?"
 msgstr "Opravdu chcete smazat tento soubor?"
 
-#: admin/class-qdr-tss-admin.php:89
+#: admin/class-qdr-tss-admin.php:91
 msgid "Are you sure you want to purge all expired files?"
 msgstr "Opravdu chcete vymazat všechny soubory s prošlou platností?"
 
-#: admin/class-qdr-tss-admin.php:90
+#: admin/class-qdr-tss-admin.php:92
+msgid "Are you sure you want to force activate this file? This will reset the active period starting from now."
+msgstr "Opravdu chcete vynutit aktivaci tohoto souboru? Tím se resetuje aktivní období začínající od teď."
+
+#: admin/class-qdr-tss-admin.php:93
 msgid "Error loading items."
 msgstr "Chyba při načítání položek."
 
-#: admin/class-qdr-tss-admin.php:91
+#: admin/class-qdr-tss-admin.php:94
 msgid "Error loading items. Please try again."
 msgstr "Chyba při načítání položek. Zkuste to prosím znovu."
 
-#: admin/class-qdr-tss-admin.php:92
+#: admin/class-qdr-tss-admin.php:95
 msgid "Error loading item details."
 msgstr "Chyba při načítání detailů položky."
 
-#: admin/class-qdr-tss-admin.php:93
+#: admin/class-qdr-tss-admin.php:96
 msgid "Error loading item details. Please try again."
 msgstr "Chyba při načítání detailů položky. Zkuste to prosím znovu."
 
-#: admin/class-qdr-tss-admin.php:94
+#: admin/class-qdr-tss-admin.php:97
 msgid "Changes saved successfully."
 msgstr "Změny byly úspěšně uloženy."
 
-#: admin/class-qdr-tss-admin.php:95
+#: admin/class-qdr-tss-admin.php:98
 msgid "Error saving changes."
 msgstr "Chyba při ukládání změn."
 
-#: admin/class-qdr-tss-admin.php:96
+#: admin/class-qdr-tss-admin.php:99
 msgid "Error saving changes. Please try again."
 msgstr "Chyba při ukládání změn. Zkuste to prosím znovu."
 
-#: admin/class-qdr-tss-admin.php:97
+#: admin/class-qdr-tss-admin.php:100
 msgid "File deleted successfully."
 msgstr "Soubor byl úspěšně smazán."
 
-#: admin/class-qdr-tss-admin.php:98
+#: admin/class-qdr-tss-admin.php:101
 msgid "Error deleting file."
 msgstr "Chyba při mazání souboru."
 
-#: admin/class-qdr-tss-admin.php:99
+#: admin/class-qdr-tss-admin.php:102
 msgid "Error deleting file. Please try again."
 msgstr "Chyba při mazání souboru. Zkuste to prosím znovu."
 
-#: admin/class-qdr-tss-admin.php:100
+#: admin/class-qdr-tss-admin.php:103
 msgid "Shortcode copied to clipboard."
 msgstr "Shortcode zkopírován do schránky."
 
-#: admin/class-qdr-tss-admin.php:101
+#: admin/class-qdr-tss-admin.php:104
 msgid "Permalink copied to clipboard."
 msgstr "Permalink zkopírován do schránky."
 
+#: admin/class-qdr-tss-admin.php:105
+msgid "Permalink copied to clipboard successfully."
+msgstr "Permalink byl úspěšně zkopírován do schránky."
+
+#: admin/class-qdr-tss-admin.php:106
+msgid "Error getting permalink."
+msgstr "Chyba při získávání permalinku."
+
+#: admin/class-qdr-tss-admin.php:107
+msgid "Error getting permalink. Please try again."
+msgstr "Chyba při získávání permalinku. Zkuste to prosím znovu."
+
+#: admin/class-qdr-tss-admin.php:108
+msgctxt "Admin success message"
+msgid "Item has been force activated successfully."
+msgstr "Položka byla úspěšně vynuceně aktivována."
+
+#: admin/class-qdr-tss-admin.php:109
+msgid "Error force activating item."
+msgstr "Chyba při vynucené aktivaci položky."
+
+#: admin/class-qdr-tss-admin.php:110
+msgid "Error force activating item. Please try again."
+msgstr "Chyba při vynucené aktivaci položky. Zkuste to prosím znovu."
+
+#: admin/class-qdr-tss-admin.php:111
+msgid "Invalid date format"
+msgstr "Neplatný formát data"
+
+#: admin/class-qdr-tss-admin.php:112
+msgid "Active From date must be before Active To date"
+msgstr "Datum 'Aktivní od' musí být před datem 'Aktivní do'"
+
 #: admin/class-qdr-tss-admin.php:133 admin/class-qdr-tss-admin.php:142
 #: admin/partials/media-page.php:16
 msgid "Shared Storage"
@@ -164,6 +211,7 @@ msgstr "Nastavení sdíleného úložiště"
 #: admin/class-qdr-tss-media-page.php:156
 #: admin/class-qdr-tss-media-page.php:186
 #: admin/class-qdr-tss-media-page.php:232
+#: admin/class-qdr-tss-media-page.php:278
 #: admin/class-qdr-tss-settings-page.php:32
 #: admin/class-qdr-tss-settings-page.php:44 class-qdr-tss-download.php:102
 msgid "Security check failed."
@@ -171,11 +219,13 @@ msgstr "Bezpečnostní kontrola selhala."
 
 #: admin/class-qdr-tss-media-page.php:160
 #: admin/class-qdr-tss-media-page.php:236
+#: admin/class-qdr-tss-media-page.php:282
 msgid "Missing item ID."
 msgstr "Chybí ID položky."
 
 #: admin/class-qdr-tss-media-page.php:165
 #: admin/class-qdr-tss-media-page.php:241
+#: admin/class-qdr-tss-media-page.php:289
 msgid "Item not found."
 msgstr "Položka nebyla nalezena."
 
@@ -200,6 +250,33 @@ msgstr "Nepodařilo se smazat položku."
 msgid "Item deleted successfully."
 msgstr "Položka byla úspěšně smazána."
 
+#: admin/class-qdr-tss-media-page.php:309
+msgid "Failed to force activate item."
+msgstr "Nepodařilo se vynutit aktivaci položky."
+
+#: admin/class-qdr-tss-media-page.php:314
+msgctxt "AJAX success message"
+msgid "Item has been force activated successfully."
+msgstr "Položka byla úspěšně vynuceně aktivována."
+
+#: admin/class-qdr-tss-media-page.php:320
+#: class-qdr-tss-media-controller.php:250
+#: class-qdr-tss-upload-controller.php:285
+msgid "Invalid Active From date format."
+msgstr "Neplatný formát data 'Aktivní od'."
+
+#: admin/class-qdr-tss-media-page.php:325
+#: class-qdr-tss-media-controller.php:255
+#: class-qdr-tss-upload-controller.php:290
+msgid "Invalid Active To date format."
+msgstr "Neplatný formát data 'Aktivní do'."
+
+#: admin/class-qdr-tss-media-page.php:330
+#: class-qdr-tss-media-controller.php:260
+#: class-qdr-tss-upload-controller.php:295
+msgid "Active From date must be before Active To date."
+msgstr "Datum 'Aktivní od' musí být před datem 'Aktivní do'."
+
 #: admin/class-qdr-tss-settings-page.php:56
 msgid "Settings saved successfully."
 msgstr "Nastavení bylo úspěšně uloženo."
@@ -235,12 +312,13 @@ msgstr "Resetovat"
 msgid "Refresh"
 msgstr "Obnovit"
 
-#: admin/partials/media-page.php:54 admin/partials/media-page.php:130
-#: class-qdr-tss-download.php:180
-msgid "Media ID"
-msgstr "ID média"
+#: admin/partials/media-page.php:54
+msgctxt "Table column header"
+msgid "File ID"
+msgstr "ID souboru"
 
-#: admin/partials/media-page.php:55 admin/partials/media-page.php:134
+#: admin/partials/media-page.php:55
+msgctxt "Table column header"
 msgid "File Name"
 msgstr "Název souboru"
 
@@ -299,6 +377,16 @@ msgstr "Zrušit"
 msgid "File Details"
 msgstr "Detaily souboru"
 
+#: admin/partials/media-page.php:130
+msgctxt "Details modal label"
+msgid "File ID"
+msgstr "ID souboru"
+
+#: admin/partials/media-page.php:134
+msgctxt "Details modal label"
+msgid "File Name"
+msgstr "Název souboru"
+
 #: admin/partials/media-page.php:150
 msgid "Active Period"
 msgstr "Aktivní období"
@@ -324,6 +412,7 @@ msgid "Copy Shortcode"
 msgstr "Kopírovat shortcode"
 
 #: admin/partials/media-page.php:178
+msgctxt "Details modal button"
 msgid "Copy Permalink"
 msgstr "Kopírovat permalink"
 
@@ -340,6 +429,11 @@ msgid ""
 "Are you sure you want to delete this file? This action cannot be undone."
 msgstr "Opravdu chcete smazat tento soubor? Tuto akci nelze vrátit zpět."
 
+#: admin/partials/media-page.php:194
+msgctxt "Confirmation dialog button"
+msgid "Delete"
+msgstr "Smazat"
+
 #: admin/partials/settings-page.php:39
 msgid "API Key"
 msgstr "API klíč"
@@ -433,6 +527,11 @@ msgstr "Popis:"
 msgid "Message:"
 msgstr "Zpráva:"
 
+#: class-qdr-tss-download.php:180
+msgctxt "Download form label"
+msgid "File ID"
+msgstr "ID souboru"
+
 #: class-qdr-tss-download.php:186
 msgid "Password"
 msgstr "Heslo"
@@ -515,4 +614,4 @@ msgstr "Vypršelo"
 
 #: functions.php:90
 msgid "Active"
-msgstr "Aktivní"
+msgstr "Aktivní"

+ 122 - 32
qdr-temporary-shared-storage/languages/qdr-temporary-shared-storage.pot

@@ -83,65 +83,112 @@ msgid "Edit"
 msgstr ""
 
 #: admin/class-qdr-tss-admin.php:87
+msgctxt "Admin action button"
 msgid "Delete"
 msgstr ""
 
 #: admin/class-qdr-tss-admin.php:88
-msgid "Are you sure you want to delete this file?"
+msgctxt "Admin action button"
+msgid "Copy Permalink"
 msgstr ""
 
 #: admin/class-qdr-tss-admin.php:89
-msgid "Are you sure you want to purge all expired files?"
+msgid "Force Activate"
 msgstr ""
 
 #: admin/class-qdr-tss-admin.php:90
-msgid "Error loading items."
+msgid "Are you sure you want to delete this file?"
 msgstr ""
 
 #: admin/class-qdr-tss-admin.php:91
-msgid "Error loading items. Please try again."
+msgid "Are you sure you want to purge all expired files?"
 msgstr ""
 
 #: admin/class-qdr-tss-admin.php:92
-msgid "Error loading item details."
+msgid "Are you sure you want to force activate this file? This will reset the active period starting from now."
 msgstr ""
 
 #: admin/class-qdr-tss-admin.php:93
-msgid "Error loading item details. Please try again."
+msgid "Error loading items."
 msgstr ""
 
 #: admin/class-qdr-tss-admin.php:94
-msgid "Changes saved successfully."
+msgid "Error loading items. Please try again."
 msgstr ""
 
 #: admin/class-qdr-tss-admin.php:95
-msgid "Error saving changes."
+msgid "Error loading item details."
 msgstr ""
 
 #: admin/class-qdr-tss-admin.php:96
-msgid "Error saving changes. Please try again."
+msgid "Error loading item details. Please try again."
 msgstr ""
 
 #: admin/class-qdr-tss-admin.php:97
-msgid "File deleted successfully."
+msgid "Changes saved successfully."
 msgstr ""
 
 #: admin/class-qdr-tss-admin.php:98
-msgid "Error deleting file."
+msgid "Error saving changes."
 msgstr ""
 
 #: admin/class-qdr-tss-admin.php:99
-msgid "Error deleting file. Please try again."
+msgid "Error saving changes. Please try again."
 msgstr ""
 
 #: admin/class-qdr-tss-admin.php:100
-msgid "Shortcode copied to clipboard."
+msgid "File deleted successfully."
 msgstr ""
 
 #: admin/class-qdr-tss-admin.php:101
+msgid "Error deleting file."
+msgstr ""
+
+#: admin/class-qdr-tss-admin.php:102
+msgid "Error deleting file. Please try again."
+msgstr ""
+
+#: admin/class-qdr-tss-admin.php:103
+msgid "Shortcode copied to clipboard."
+msgstr ""
+
+#: admin/class-qdr-tss-admin.php:104
 msgid "Permalink copied to clipboard."
 msgstr ""
 
+#: admin/class-qdr-tss-admin.php:105
+msgid "Permalink copied to clipboard successfully."
+msgstr ""
+
+#: admin/class-qdr-tss-admin.php:106
+msgid "Error getting permalink."
+msgstr ""
+
+#: admin/class-qdr-tss-admin.php:107
+msgid "Error getting permalink. Please try again."
+msgstr ""
+
+#: admin/class-qdr-tss-admin.php:108
+msgctxt "Admin success message"
+msgid "Item has been force activated successfully."
+msgstr ""
+
+#: admin/class-qdr-tss-admin.php:109
+msgid "Error force activating item."
+msgstr ""
+
+#: admin/class-qdr-tss-admin.php:110
+msgid "Error force activating item. Please try again."
+msgstr ""
+
+#: admin/class-qdr-tss-admin.php:111
+msgid "Invalid date format"
+msgstr ""
+
+#: admin/class-qdr-tss-admin.php:112
+msgid "Active From date must be before Active To date"
+msgstr ""
+
 #: admin/class-qdr-tss-admin.php:133
 #: admin/class-qdr-tss-admin.php:142
 msgid "Shared Storage"
@@ -155,14 +202,19 @@ msgstr ""
 #: admin/class-qdr-tss-media-page.php:156
 #: admin/class-qdr-tss-media-page.php:186
 #: admin/class-qdr-tss-media-page.php:232
+#: admin/class-qdr-tss-media-page.php:278
 msgid "Security check failed."
 msgstr ""
 
 #: admin/class-qdr-tss-media-page.php:160
+#: admin/class-qdr-tss-media-page.php:236
+#: admin/class-qdr-tss-media-page.php:282
 msgid "Missing item ID."
 msgstr ""
 
 #: admin/class-qdr-tss-media-page.php:165
+#: admin/class-qdr-tss-media-page.php:241
+#: admin/class-qdr-tss-media-page.php:289
 msgid "Item not found."
 msgstr ""
 
@@ -178,14 +230,6 @@ msgstr ""
 msgid "Item updated successfully."
 msgstr ""
 
-#: admin/class-qdr-tss-media-page.php:236
-msgid "Missing item ID."
-msgstr ""
-
-#: admin/class-qdr-tss-media-page.php:241
-msgid "Item not found."
-msgstr ""
-
 #: admin/class-qdr-tss-media-page.php:253
 msgid "Failed to delete item."
 msgstr ""
@@ -194,6 +238,27 @@ msgstr ""
 msgid "Item deleted successfully."
 msgstr ""
 
+#: admin/class-qdr-tss-media-page.php:309
+msgid "Failed to force activate item."
+msgstr ""
+
+#: admin/class-qdr-tss-media-page.php:314
+msgctxt "AJAX success message"
+msgid "Item has been force activated successfully."
+msgstr ""
+
+#: admin/class-qdr-tss-media-page.php:320
+msgid "Invalid Active From date format."
+msgstr ""
+
+#: admin/class-qdr-tss-media-page.php:325
+msgid "Invalid Active To date format."
+msgstr ""
+
+#: admin/class-qdr-tss-media-page.php:330
+msgid "Active From date must be before Active To date."
+msgstr ""
+
 #: admin/class-qdr-tss-settings-page.php:32
 #: admin/class-qdr-tss-settings-page.php:44
 msgid "Security check failed."
@@ -238,10 +303,12 @@ msgid "Refresh"
 msgstr ""
 
 #: admin/partials/media-page.php:54
-msgid "Media ID"
+msgctxt "Table column header"
+msgid "File ID"
 msgstr ""
 
 #: admin/partials/media-page.php:55
+msgctxt "Table column header"
 msgid "File Name"
 msgstr ""
 
@@ -318,10 +385,12 @@ msgid "File Details"
 msgstr ""
 
 #: admin/partials/media-page.php:130
-msgid "Media ID"
+msgctxt "Details modal label"
+msgid "File ID"
 msgstr ""
 
 #: admin/partials/media-page.php:134
+msgctxt "Details modal label"
 msgid "File Name"
 msgstr ""
 
@@ -366,6 +435,7 @@ msgid "Copy Shortcode"
 msgstr ""
 
 #: admin/partials/media-page.php:178
+msgctxt "Details modal button"
 msgid "Copy Permalink"
 msgstr ""
 
@@ -382,6 +452,7 @@ msgid "Are you sure you want to delete this file? This action cannot be undone."
 msgstr ""
 
 #: admin/partials/media-page.php:194
+msgctxt "Confirmation dialog button"
 msgid "Delete"
 msgstr ""
 
@@ -493,7 +564,8 @@ msgid "Message:"
 msgstr ""
 
 #: class-qdr-tss-download.php:180
-msgid "Media ID"
+msgctxt "Download form label"
+msgid "File ID"
 msgstr ""
 
 #: class-qdr-tss-download.php:186
@@ -526,11 +598,25 @@ msgstr ""
 msgid "Failed to delete media."
 msgstr ""
 
+#: class-qdr-tss-media-controller.php:250
+msgid "Invalid Active From date format."
+msgstr ""
+
+#: class-qdr-tss-media-controller.php:255
+msgid "Invalid Active To date format."
+msgstr ""
+
+#: class-qdr-tss-media-controller.php:260
+msgid "Active From date must be before Active To date."
+msgstr ""
+
 #: class-qdr-tss-rest-controller.php:80
 msgid "Invalid API key."
 msgstr ""
 
 #: class-qdr-tss-upload-controller.php:99
+#: class-qdr-tss-upload-controller.php:146
+#: class-qdr-tss-upload-controller.php:194
 msgid "Missing required parameters."
 msgstr ""
 
@@ -546,10 +632,6 @@ msgstr ""
 msgid "Failed to initialize upload."
 msgstr ""
 
-#: class-qdr-tss-upload-controller.php:146
-msgid "Missing required parameters."
-msgstr ""
-
 #: class-qdr-tss-upload-controller.php:159
 msgid "No file uploaded."
 msgstr ""
@@ -558,10 +640,6 @@ msgstr ""
 msgid "Failed to upload chunk."
 msgstr ""
 
-#: class-qdr-tss-upload-controller.php:194
-msgid "Missing required parameters."
-msgstr ""
-
 #: class-qdr-tss-upload-controller.php:204
 msgid "Missing required field: %s"
 msgstr ""
@@ -578,6 +656,18 @@ msgstr ""
 msgid "Failed to save file information."
 msgstr ""
 
+#: class-qdr-tss-upload-controller.php:285
+msgid "Invalid Active From date format."
+msgstr ""
+
+#: class-qdr-tss-upload-controller.php:290
+msgid "Invalid Active To date format."
+msgstr ""
+
+#: class-qdr-tss-upload-controller.php:295
+msgid "Active From date must be before Active To date."
+msgstr ""
+
 #: functions.php:86
 msgid "Pending"
 msgstr ""

+ 64 - 6
qdr-temporary-shared-storage/rest-api/class-qdr-tss-media-controller.php

@@ -162,7 +162,7 @@ class QDR_TSS_Media_Controller {
         if (!$item) {
             return new WP_Error(
                 'qdr_tss_not_found',
-                __('File not found.', 'qdr-temporary-shared-storage'),
+                __('Media not found.', 'qdr-temporary-shared-storage'),
                 array('status' => 404)
             );
         }
@@ -188,7 +188,7 @@ class QDR_TSS_Media_Controller {
         if (!$item) {
             return new WP_Error(
                 'qdr_tss_not_found',
-                __('File not found.', 'qdr-temporary-shared-storage'),
+                __('Media not found.', 'qdr-temporary-shared-storage'),
                 array('status' => 404)
             );
         }
@@ -216,6 +216,12 @@ class QDR_TSS_Media_Controller {
             $data['description'] = sanitize_textarea_field($request['description']);
         }
         
+        // Validate the active period dates
+        $validation_result = $this->validate_active_period($data);
+        if (is_wp_error($validation_result)) {
+            return $validation_result;
+        }
+        
         if (empty($data)) {
             return new WP_Error(
                 'qdr_tss_no_changes',
@@ -229,7 +235,7 @@ class QDR_TSS_Media_Controller {
         if (!$result) {
             return new WP_Error(
                 'qdr_tss_update_failed',
-                __('Failed to update file.', 'qdr-temporary-shared-storage'),
+                __('Failed to update media.', 'qdr-temporary-shared-storage'),
                 array('status' => 500)
             );
         }
@@ -242,6 +248,58 @@ class QDR_TSS_Media_Controller {
         return new WP_REST_Response($updated_item, 200);
     }
 
+    /**
+     * Validate active period dates.
+     *
+     * @since    1.0.0
+     * @param    array    $data    The data array containing active_from and active_to
+     * @return   true|WP_Error    True if valid, WP_Error if invalid
+     */
+    private function validate_active_period($data) {
+        // If both dates are provided, validate them
+        if (isset($data['active_from']) && isset($data['active_to'])) {
+            $active_from = $data['active_from'];
+            $active_to = $data['active_to'];
+            
+            // Skip validation if either field is empty
+            if (empty($active_from) || empty($active_to)) {
+                return true;
+            }
+            
+            // Parse dates
+            $from_timestamp = strtotime($active_from);
+            $to_timestamp = strtotime($active_to);
+            
+            // Check if dates are valid
+            if ($from_timestamp === false) {
+                return new WP_Error(
+                    'invalid_active_from',
+                    __('Invalid Active From date format.', 'qdr-temporary-shared-storage'),
+                    array('status' => 400)
+                );
+            }
+            
+            if ($to_timestamp === false) {
+                return new WP_Error(
+                    'invalid_active_to',
+                    __('Invalid Active To date format.', 'qdr-temporary-shared-storage'),
+                    array('status' => 400)
+                );
+            }
+            
+            // Check if active_from is greater than or equal to active_to
+            if ($from_timestamp >= $to_timestamp) {
+                return new WP_Error(
+                    'invalid_active_period',
+                    __('Active From date must be before Active To date.', 'qdr-temporary-shared-storage'),
+                    array('status' => 400)
+                );
+            }
+        }
+        
+        return true;
+    }
+
     /**
      * Delete a single item.
      *
@@ -257,7 +315,7 @@ class QDR_TSS_Media_Controller {
         if (!$item) {
             return new WP_Error(
                 'qdr_tss_not_found',
-                __('File not found.', 'qdr-temporary-shared-storage'),
+                __('Media not found.', 'qdr-temporary-shared-storage'),
                 array('status' => 404)
             );
         }
@@ -271,7 +329,7 @@ class QDR_TSS_Media_Controller {
         if (!$result) {
             return new WP_Error(
                 'qdr_tss_delete_failed',
-                __('Failed to delete file.', 'qdr-temporary-shared-storage'),
+                __('Failed to delete media.', 'qdr-temporary-shared-storage'),
                 array('status' => 500)
             );
         }
@@ -281,4 +339,4 @@ class QDR_TSS_Media_Controller {
             'fileid' => $id
         ), 200);
     }
-}
+}

+ 66 - 5
qdr-temporary-shared-storage/rest-api/class-qdr-tss-upload-controller.php

@@ -254,6 +254,19 @@ class QDR_TSS_Upload_Controller {
             }
         }
         
+        // Validate active period dates if provided
+        $active_from = isset($request['active_from']) ? $request['active_from'] : current_time('mysql');
+        $active_to = isset($request['active_to']) ? $request['active_to'] : date('Y-m-d H:i:s', strtotime('+30 days'));
+        
+        $validation_result = $this->validate_active_period(array(
+            'active_from' => $active_from,
+            'active_to' => $active_to
+        ));
+        
+        if (is_wp_error($validation_result)) {
+            return $validation_result;
+        }
+        
         // Check that all chunks are present
         if (!$this->file_manager->check_all_chunks($upload_id, $total_chunks)) {
             return new WP_Error(
@@ -281,10 +294,6 @@ class QDR_TSS_Upload_Controller {
         $file_path = $result['file_path'];
         $file_size = $result['file_size'];
         
-        // Set dates
-        $active_from = !empty($request['active_from']) ? $request['active_from'] : current_time('mysql');
-        $active_to = !empty($request['active_to']) ? $request['active_to'] : date('Y-m-d H:i:s', strtotime('+30 days'));
-        
         // Generate a unique fileid
         $fileid = $this->file_manager->generate_fileid();
         
@@ -325,4 +334,56 @@ class QDR_TSS_Upload_Controller {
             'shortcode' => $shortcode
         ), 201);
     }
-}
+
+    /**
+     * Validate active period dates.
+     *
+     * @since    1.0.0
+     * @param    array    $data    The data array containing active_from and active_to
+     * @return   true|WP_Error    True if valid, WP_Error if invalid
+     */
+    private function validate_active_period($data) {
+        // If both dates are provided, validate them
+        if (isset($data['active_from']) && isset($data['active_to'])) {
+            $active_from = $data['active_from'];
+            $active_to = $data['active_to'];
+            
+            // Skip validation if either field is empty
+            if (empty($active_from) || empty($active_to)) {
+                return true;
+            }
+            
+            // Parse dates
+            $from_timestamp = strtotime($active_from);
+            $to_timestamp = strtotime($active_to);
+            
+            // Check if dates are valid
+            if ($from_timestamp === false) {
+                return new WP_Error(
+                    'invalid_active_from',
+                    __('Invalid Active From date format.', 'qdr-temporary-shared-storage'),
+                    array('status' => 400)
+                );
+            }
+            
+            if ($to_timestamp === false) {
+                return new WP_Error(
+                    'invalid_active_to',
+                    __('Invalid Active To date format.', 'qdr-temporary-shared-storage'),
+                    array('status' => 400)
+                );
+            }
+            
+            // Check if active_from is greater than or equal to active_to
+            if ($from_timestamp >= $to_timestamp) {
+                return new WP_Error(
+                    'invalid_active_period',
+                    __('Active From date must be before Active To date.', 'qdr-temporary-shared-storage'),
+                    array('status' => 400)
+                );
+            }
+        }
+        
+        return true;
+    }
+}