Преглед изворни кода

qdr-temporary-shared-storage: change implementation to oop

Dalibor Votruba пре 1 година
родитељ
комит
1a3b4888d9
39 измењених фајлова са 8332 додато и 2419 уклоњено
  1. 1730 0
      qdr-temporary-shared-storage/@documentation/integration-guide.md
  2. 241 0
      qdr-temporary-shared-storage/@documentation/plugin-architecture-diagram.mermaid
  3. 0 0
      qdr-temporary-shared-storage/@documentation/plugin-architecture-diagram.svg
  4. 239 0
      qdr-temporary-shared-storage/@documentation/plugin-structure.md
  5. 595 0
      qdr-temporary-shared-storage/@documentation/rest-api-docs.md
  6. 496 0
      qdr-temporary-shared-storage/@documentation/testing-guide.md
  7. 167 0
      qdr-temporary-shared-storage/admin/class-qdr-tss-admin.php
  8. 227 0
      qdr-temporary-shared-storage/admin/class-qdr-tss-media-page.php
  9. 138 0
      qdr-temporary-shared-storage/admin/class-qdr-tss-settings-page.php
  10. 63 32
      qdr-temporary-shared-storage/admin/css/admin.css
  11. 662 0
      qdr-temporary-shared-storage/admin/js/admin.js
  12. 0 704
      qdr-temporary-shared-storage/admin/media-page.php
  13. 210 0
      qdr-temporary-shared-storage/admin/partials/media-page.php
  14. 81 0
      qdr-temporary-shared-storage/admin/partials/settings-page.php
  15. 0 173
      qdr-temporary-shared-storage/admin/settings-page.php
  16. 0 84
      qdr-temporary-shared-storage/assets/js/admin.js
  17. 0 26
      qdr-temporary-shared-storage/directory-structure.txt
  18. 67 0
      qdr-temporary-shared-storage/includes/class-qdr-tss-activator.php
  19. 117 0
      qdr-temporary-shared-storage/includes/class-qdr-tss-cron-manager.php
  20. 423 0
      qdr-temporary-shared-storage/includes/class-qdr-tss-db-manager.php
  21. 29 0
      qdr-temporary-shared-storage/includes/class-qdr-tss-deactivator.php
  22. 352 0
      qdr-temporary-shared-storage/includes/class-qdr-tss-file-manager.php
  23. 25 0
      qdr-temporary-shared-storage/includes/class-qdr-tss-i18n.php
  24. 110 0
      qdr-temporary-shared-storage/includes/class-qdr-tss-loader.php
  25. 186 0
      qdr-temporary-shared-storage/includes/class-qdr-tss-settings.php
  26. 290 0
      qdr-temporary-shared-storage/includes/class-qdr-tss.php
  27. 127 0
      qdr-temporary-shared-storage/includes/functions.php
  28. 207 0
      qdr-temporary-shared-storage/public/class-qdr-tss-download.php
  29. 97 0
      qdr-temporary-shared-storage/public/class-qdr-tss-public.php
  30. 69 0
      qdr-temporary-shared-storage/public/class-qdr-tss-shortcode.php
  31. 61 0
      qdr-temporary-shared-storage/public/css/public.css
  32. 14 0
      qdr-temporary-shared-storage/public/js/public.js
  33. 28 1200
      qdr-temporary-shared-storage/qdr-temporary-shared-storage.php
  34. 464 115
      qdr-temporary-shared-storage/readme.md
  35. 0 85
      qdr-temporary-shared-storage/readme.txt
  36. 284 0
      qdr-temporary-shared-storage/rest-api/class-qdr-tss-media-controller.php
  37. 132 0
      qdr-temporary-shared-storage/rest-api/class-qdr-tss-rest-controller.php
  38. 328 0
      qdr-temporary-shared-storage/rest-api/class-qdr-tss-upload-controller.php
  39. 73 0
      qdr-temporary-shared-storage/uninstall.php

+ 1730 - 0
qdr-temporary-shared-storage/@documentation/integration-guide.md

@@ -0,0 +1,1730 @@
+# QDR Temporary Shared Storage - Integration Guide
+
+This guide provides instructions and examples for integrating the QDR Temporary Shared Storage plugin with various applications and systems.
+
+## Table of Contents
+
+1. [Web Applications](#web-applications)
+2. [Mobile Applications](#mobile-applications)
+3. [Desktop Applications](#desktop-applications)
+4. [CRM Systems](#crm-systems)
+5. [Document Management Systems](#document-management-systems)
+6. [Email Systems](#email-systems)
+7. [Workflow Automation](#workflow-automation)
+
+## Web Applications
+
+### Basic HTML Form Integration
+
+You can create a simple HTML form that interacts with the plugin's REST API.
+
+```html
+<!DOCTYPE html>
+<html>
+<head>
+    <title>File Upload</title>
+    <style>
+        .progress {
+            width: 100%;
+            height: 20px;
+            background-color: #f3f3f3;
+            margin-top: 10px;
+        }
+        .progress-bar {
+            height: 100%;
+            background-color: #4CAF50;
+            width: 0%;
+            transition: width 0.3s;
+        }
+    </style>
+</head>
+<body>
+    <h1>Upload a File</h1>
+    
+    <form id="upload-form">
+        <div>
+            <label for="file">File:</label>
+            <input type="file" id="file" required>
+        </div>
+        <div>
+            <label for="description">Description:</label>
+            <input type="text" id="description" required>
+        </div>
+        <div>
+            <label for="message">Message:</label>
+            <textarea id="message"></textarea>
+        </div>
+        <div>
+            <label for="password">Password:</label>
+            <input type="password" id="password" required>
+        </div>
+        <div>
+            <label for="reference">Reference:</label>
+            <input type="text" id="reference">
+        </div>
+        <div>
+            <label for="active-days">Active for (days):</label>
+            <input type="number" id="active-days" value="30" min="1" max="365">
+        </div>
+        
+        <button type="submit">Upload</button>
+    </form>
+    
+    <div class="progress">
+        <div class="progress-bar" id="progress-bar"></div>
+    </div>
+    
+    <div id="result"></div>
+    
+    <script>
+        document.getElementById('upload-form').addEventListener('submit', async function(e) {
+            e.preventDefault();
+            
+            const apiKey = 'your-api-key-here';
+            const apiUrl = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
+            const file = document.getElementById('file').files[0];
+            
+            if (!file) {
+                alert('Please select a file');
+                return;
+            }
+            
+            const description = document.getElementById('description').value;
+            const message = document.getElementById('message').value;
+            const password = document.getElementById('password').value;
+            const reference = document.getElementById('reference').value;
+            const activeDays = document.getElementById('active-days').value;
+            
+            const progressBar = document.getElementById('progress-bar');
+            const resultDiv = document.getElementById('result');
+            
+            try {
+                // Call the uploadSharedFile function
+                resultDiv.innerHTML = 'Uploading...';
+                const result = await uploadSharedFile(
+                    file,
+                    description,
+                    password,
+                    reference,
+                    message,
+                    activeDays,
+                    function(progress) {
+                        progressBar.style.width = progress + '%';
+                    }
+                );
+                
+                // Display result
+                resultDiv.innerHTML = `
+                    <h2>File Uploaded Successfully!</h2>
+                    <p>Media ID: ${result.media_id}</p>
+                    <p>Download Link: <a href="${result.permalink}" target="_blank">${result.permalink}</a></p>
+                    <p>Shortcode: <code>${result.shortcode}</code></p>
+                `;
+            } catch (error) {
+                resultDiv.innerHTML = `<p style="color: red;">Error: ${error.message}</p>`;
+            }
+        });
+        
+        // Upload function
+        async function uploadSharedFile(file, description, password, reference = '', message = '', activeDays = 30, progressCallback = null) {
+            const apiKey = 'your-api-key-here';
+            const apiUrl = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
+            
+            const chunkSize = 1024 * 1024; // 1MB chunks
+            const totalChunks = Math.ceil(file.size / chunkSize);
+            
+            // Step 1: Initialize upload
+            const initResponse = await fetch(`${apiUrl}/upload/init`, {
+                method: 'POST',
+                headers: {
+                    'X-Api-Key': apiKey,
+                    'Content-Type': 'application/json'
+                },
+                body: JSON.stringify({
+                    original_file_name: file.name,
+                    file_size: file.size
+                })
+            });
+            
+            const initResult = await initResponse.json();
+            if (!initResult.upload_id) {
+                throw new Error('Failed to initialize upload');
+            }
+            
+            const uploadId = initResult.upload_id;
+            
+            // Step 2: Upload chunks
+            for (let i = 0; i < totalChunks; i++) {
+                const start = i * chunkSize;
+                const end = Math.min(file.size, start + chunkSize);
+                const chunk = file.slice(start, end);
+                
+                const formData = new FormData();
+                formData.append('upload_id', uploadId);
+                formData.append('chunk_index', i);
+                formData.append('total_chunks', totalChunks);
+                formData.append('chunk', chunk);
+                
+                const chunkResponse = await fetch(`${apiUrl}/upload/chunk`, {
+                    method: 'POST',
+                    headers: {
+                        'X-Api-Key': apiKey
+                    },
+                    body: formData
+                });
+                
+                const chunkResult = await chunkResponse.json();
+                if (!chunkResult.status || chunkResult.status !== 'chunk_uploaded') {
+                    throw new Error(`Failed to upload chunk ${i}`);
+                }
+                
+                // Update progress
+                if (progressCallback) {
+                    progressCallback((i + 1) / totalChunks * 100);
+                }
+            }
+            
+            // Step 3: Finalize upload
+            const activeToDate = new Date();
+            activeToDate.setDate(activeToDate.getDate() + parseInt(activeDays)); // X days from now
+            const activeTo = activeToDate.toISOString().slice(0, 19).replace('T', ' ');
+            
+            const finalizeResponse = await fetch(`${apiUrl}/upload/finalize`, {
+                method: 'POST',
+                headers: {
+                    'X-Api-Key': apiKey,
+                    'Content-Type': 'application/json'
+                },
+                body: JSON.stringify({
+                    upload_id: uploadId,
+                    total_chunks: totalChunks,
+                    original_file_name: file.name,
+                    description: description,
+                    message: message,
+                    password: password,
+                    reference: reference,
+                    active_to: activeTo
+                })
+            });
+            
+            const finalResult = await finalizeResponse.json();
+            if (!finalResult.media_id) {
+                throw new Error('Failed to finalize upload');
+            }
+            
+            return finalResult;
+        }
+    </script>
+</body>
+</html>
+```
+
+### React Component Integration
+
+Create a reusable React component for file uploads:
+
+```jsx
+import React, { useState } from 'react';
+import axios from 'axios';
+
+const FileUploader = ({ apiKey, apiUrl, onSuccess, onError }) => {
+  const [file, setFile] = useState(null);
+  const [description, setDescription] = useState('');
+  const [message, setMessage] = useState('');
+  const [password, setPassword] = useState('');
+  const [reference, setReference] = useState('');
+  const [activeDays, setActiveDays] = useState(30);
+  const [progress, setProgress] = useState(0);
+  const [uploading, setUploading] = useState(false);
+  
+  const handleFileChange = (e) => {
+    setFile(e.target.files[0]);
+  };
+  
+  const handleSubmit = async (e) => {
+    e.preventDefault();
+    if (!file) return;
+    
+    setUploading(true);
+    setProgress(0);
+    
+    try {
+      // Step 1: Initialize upload
+      const initResponse = await axios.post(`${apiUrl}/upload/init`, {
+        original_file_name: file.name,
+        file_size: file.size
+      }, {
+        headers: { 'X-Api-Key': apiKey }
+      });
+      
+      const uploadId = initResponse.data.upload_id;
+      
+      // Step 2: Upload chunks
+      const chunkSize = 1024 * 1024; // 1MB chunks
+      const totalChunks = Math.ceil(file.size / chunkSize);
+      
+      for (let i = 0; i < totalChunks; i++) {
+        const start = i * chunkSize;
+        const end = Math.min(file.size, start + chunkSize);
+        const chunk = file.slice(start, end);
+        
+        const formData = new FormData();
+        formData.append('upload_id', uploadId);
+        formData.append('chunk_index', i);
+        formData.append('total_chunks', totalChunks);
+        formData.append('chunk', chunk);
+        
+        await axios.post(`${apiUrl}/upload/chunk`, formData, {
+          headers: { 'X-Api-Key': apiKey }
+        });
+        
+        setProgress(Math.round((i + 1) / totalChunks * 100));
+      }
+      
+      // Step 3: Finalize upload
+      const activeToDate = new Date();
+      activeToDate.setDate(activeToDate.getDate() + parseInt(activeDays));
+      const activeTo = activeToDate.toISOString().slice(0, 19).replace('T', ' ');
+      
+      const finalizeResponse = await axios.post(`${apiUrl}/upload/finalize`, {
+        upload_id: uploadId,
+        total_chunks: totalChunks,
+        original_file_name: file.name,
+        description,
+        message,
+        password,
+        reference,
+        active_to: activeTo
+      }, {
+        headers: { 'X-Api-Key': apiKey }
+      });
+      
+      setUploading(false);
+      
+      if (onSuccess) {
+        onSuccess(finalizeResponse.data);
+      }
+    } catch (error) {
+      setUploading(false);
+      
+      if (onError) {
+        onError(error);
+      }
+    }
+  };
+  
+  return (
+    <div className="file-uploader">
+      <form onSubmit={handleSubmit}>
+        <div className="form-group">
+          <label>File</label>
+          <input 
+            type="file" 
+            onChange={handleFileChange} 
+            disabled={uploading} 
+            required 
+          />
+        </div>
+        
+        <div className="form-group">
+          <label>Description</label>
+          <input 
+            type="text" 
+            value={description} 
+            onChange={(e) => setDescription(e.target.value)} 
+            disabled={uploading} 
+            required 
+          />
+        </div>
+        
+        <div className="form-group">
+          <label>Message (Optional)</label>
+          <textarea 
+            value={message} 
+            onChange={(e) => setMessage(e.target.value)} 
+            disabled={uploading} 
+          />
+        </div>
+        
+        <div className="form-group">
+          <label>Password</label>
+          <input 
+            type="password" 
+            value={password} 
+            onChange={(e) => setPassword(e.target.value)} 
+            disabled={uploading} 
+            required 
+          />
+        </div>
+        
+        <div className="form-group">
+          <label>Reference (Optional)</label>
+          <input 
+            type="text" 
+            value={reference} 
+            onChange={(e) => setReference(e.target.value)} 
+            disabled={uploading} 
+          />
+        </div>
+        
+        <div className="form-group">
+          <label>Active for (days)</label>
+          <input 
+            type="number" 
+            value={activeDays} 
+            onChange={(e) => setActiveDays(e.target.value)} 
+            min="1" 
+            max="365" 
+            disabled={uploading} 
+          />
+        </div>
+        
+        <button type="submit" disabled={uploading || !file}>
+          {uploading ? 'Uploading...' : 'Upload File'}
+        </button>
+      </form>
+      
+      {uploading && (
+        <div className="progress-container">
+          <div className="progress-bar" style={{ width: `${progress}%` }}></div>
+          <div className="progress-text">{progress}%</div>
+        </div>
+      )}
+    </div>
+  );
+};
+
+export default FileUploader;
+```
+
+Usage in a React application:
+
+```jsx
+import React, { useState } from 'react';
+import FileUploader from './FileUploader';
+
+function App() {
+  const [uploadResult, setUploadResult] = useState(null);
+  const [error, setError] = useState(null);
+  
+  const handleSuccess = (result) => {
+    setUploadResult(result);
+    setError(null);
+  };
+  
+  const handleError = (error) => {
+    setError(error.message || 'Upload failed');
+    setUploadResult(null);
+  };
+  
+  return (
+    <div className="app">
+      <h1>File Uploader Demo</h1>
+      
+      <FileUploader 
+        apiKey="your-api-key-here"
+        apiUrl="https://your-wordpress-site.com/wp-json/qdr-tss/v1"
+        onSuccess={handleSuccess}
+        onError={handleError}
+      />
+      
+      {error && (
+        <div className="error-message">
+          Error: {error}
+        </div>
+      )}
+      
+      {uploadResult && (
+        <div className="upload-result">
+          <h2>File Uploaded Successfully!</h2>
+          <p>Media ID: {uploadResult.media_id}</p>
+          <p>
+            Download Link: 
+            <a href={uploadResult.permalink} target="_blank" rel="noopener noreferrer">
+              {uploadResult.permalink}
+            </a>
+          </p>
+          <p>Shortcode: <code>{uploadResult.shortcode}</code></p>
+        </div>
+      )}
+    </div>
+  );
+}
+
+export default App;
+```
+
+## Mobile Applications
+
+### React Native Integration
+
+```javascript
+import React, { useState } from 'react';
+import { View, Text, TextInput, Button, StyleSheet, ProgressBar, Alert } from 'react-native';
+import DocumentPicker from 'react-native-document-picker';
+import RNFS from 'react-native-fs';
+
+const FileUploaderScreen = () => {
+  const [file, setFile] = useState(null);
+  const [description, setDescription] = useState('');
+  const [message, setMessage] = useState('');
+  const [password, setPassword] = useState('');
+  const [reference, setReference] = useState('');
+  const [activeDays, setActiveDays] = useState('30');
+  const [uploading, setUploading] = useState(false);
+  const [progress, setProgress] = useState(0);
+  
+  const API_KEY = 'your-api-key-here';
+  const API_URL = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
+  
+  const pickDocument = async () => {
+    try {
+      const result = await DocumentPicker.pick({
+        type: [DocumentPicker.types.allFiles],
+      });
+      
+      setFile(result[0]);
+    } catch (err) {
+      if (DocumentPicker.isCancel(err)) {
+        // User cancelled the picker
+      } else {
+        Alert.alert('Error', 'Error picking document: ' + err.message);
+      }
+    }
+  };
+  
+  const uploadFile = async () => {
+    if (!file) {
+      Alert.alert('Error', 'Please select a file first');
+      return;
+    }
+    
+    if (!description || !password) {
+      Alert.alert('Error', 'Description and password are required');
+      return;
+    }
+    
+    setUploading(true);
+    setProgress(0);
+    
+    try {
+      // Step 1: Initialize upload
+      const initResponse = await fetch(`${API_URL}/upload/init`, {
+        method: 'POST',
+        headers: {
+          'X-Api-Key': API_KEY,
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          original_file_name: file.name,
+          file_size: file.size,
+        }),
+      });
+      
+      const initData = await initResponse.json();
+      
+      if (!initData.upload_id) {
+        throw new Error('Failed to initialize upload');
+      }
+      
+      const uploadId = initData.upload_id;
+      
+      // Step 2: Upload chunks
+      const chunkSize = 1024 * 1024; // 1MB chunks
+      const totalChunks = Math.ceil(file.size / chunkSize);
+      const fileContent = await RNFS.readFile(file.uri, 'base64');
+      
+      for (let i = 0; i < totalChunks; i++) {
+        const start = i * chunkSize;
+        const end = Math.min(file.size, start + chunkSize);
+        
+        // Get chunk data
+        const chunkData = fileContent.substring(start, end);
+        
+        // Create a temporary file for the chunk
+        const tempFilePath = `${RNFS.TemporaryDirectoryPath}/chunk_${i}`;
+        await RNFS.writeFile(tempFilePath, chunkData, 'base64');
+        
+        // Upload the chunk
+        const formData = new FormData();
+        formData.append('upload_id', uploadId);
+        formData.append('chunk_index', i.toString());
+        formData.append('total_chunks', totalChunks.toString());
+        formData.append('chunk', {
+          uri: `file://${tempFilePath}`,
+          name: 'chunk',
+          type: 'application/octet-stream',
+        });
+        
+        const chunkResponse = await fetch(`${API_URL}/upload/chunk`, {
+          method: 'POST',
+          headers: {
+            'X-Api-Key': API_KEY,
+            'Content-Type': 'multipart/form-data',
+          },
+          body: formData,
+        });
+        
+        const chunkData = await chunkResponse.json();
+        
+        if (!chunkData.status || chunkData.status !== 'chunk_uploaded') {
+          throw new Error(`Failed to upload chunk ${i}`);
+        }
+        
+        // Clean up temporary file
+        await RNFS.unlink(tempFilePath);
+        
+        // Update progress
+        setProgress((i + 1) / totalChunks);
+      }
+      
+      // Step 3: Finalize upload
+      const activeToDate = new Date();
+      activeToDate.setDate(activeToDate.getDate() + parseInt(activeDays));
+      const activeTo = activeToDate.toISOString().slice(0, 19).replace('T', ' ');
+      
+      const finalizeResponse = await fetch(`${API_URL}/upload/finalize`, {
+        method: 'POST',
+        headers: {
+          'X-Api-Key': API_KEY,
+          'Content-Type': 'application/json',
+        },
+        body: JSON.stringify({
+          upload_id: uploadId,
+          total_chunks: totalChunks,
+          original_file_name: file.name,
+          description,
+          message,
+          password,
+          reference,
+          active_to: activeTo,
+        }),
+      });
+      
+      const finalizeData = await finalizeResponse.json();
+      
+      if (!finalizeData.media_id) {
+        throw new Error('Failed to finalize upload');
+      }
+      
+      setUploading(false);
+      
+      Alert.alert(
+        'Success',
+        `File uploaded successfully!\n\nMedia ID: ${finalizeData.media_id}\nDownload Link: ${finalizeData.permalink}`
+      );
+    } catch (error) {
+      setUploading(false);
+      Alert.alert('Error', error.message);
+    }
+  };
+  
+  return (
+    <View style={styles.container}>
+      <Text style={styles.title}>Upload Shared File</Text>
+      
+      <Button title="Pick a File" onPress={pickDocument} disabled={uploading} />
+      
+      {file && (
+        <Text style={styles.fileInfo}>
+          Selected: {file.name} ({(file.size / 1024 / 1024).toFixed(2)} MB)
+        </Text>
+      )}
+      
+      <TextInput
+        style={styles.input}
+        placeholder="Description"
+        value={description}
+        onChangeText={setDescription}
+        editable={!uploading}
+      />
+      
+      <TextInput
+        style={styles.input}
+        placeholder="Message (Optional)"
+        value={message}
+        onChangeText={setMessage}
+        editable={!uploading}
+        multiline
+      />
+      
+      <TextInput
+        style={styles.input}
+        placeholder="Password"
+        value={password}
+        onChangeText={setPassword}
+        editable={!uploading}
+        secureTextEntry
+      />
+      
+      <TextInput
+        style={styles.input}
+        placeholder="Reference (Optional)"
+        value={reference}
+        onChangeText={setReference}
+        editable={!uploading}
+      />
+      
+      <TextInput
+        style={styles.input}
+        placeholder="Active for (days)"
+        value={activeDays}
+        onChangeText={setActiveDays}
+        keyboardType="numeric"
+        editable={!uploading}
+      />
+      
+      {uploading && (
+        <View style={styles.progressContainer}>
+          <ProgressBar progress={progress} color="#4CAF50" />
+          <Text style={styles.progressText}>{Math.round(progress * 100)}%</Text>
+        </View>
+      )}
+      
+      <Button
+        title={uploading ? 'Uploading...' : 'Upload File'}
+        onPress={uploadFile}
+        disabled={uploading || !file}
+      />
+    </View>
+  );
+};
+
+const styles = StyleSheet.create({
+  container: {
+    flex: 1,
+    padding: 20,
+  },
+  title: {
+    fontSize: 20,
+    fontWeight: 'bold',
+    marginBottom: 20,
+  },
+  input: {
+    borderWidth: 1,
+    borderColor: '#ccc',
+    borderRadius: 5,
+    padding: 10,
+    marginVertical: 10,
+  },
+  fileInfo: {
+    marginVertical: 10,
+    fontStyle: 'italic',
+  },
+  progressContainer: {
+    marginVertical: 10,
+  },
+  progressText: {
+    textAlign: 'center',
+    marginTop: 5,
+  },
+});
+
+export default FileUploaderScreen;
+```
+
+## Desktop Applications
+
+### Electron Application Integration
+
+```javascript
+// main.js
+const { app, BrowserWindow, ipcMain } = require('electron');
+const path = require('path');
+const fs = require('fs');
+const axios = require('axios');
+const FormData = require('form-data');
+
+let mainWindow;
+
+function createWindow() {
+  mainWindow = new BrowserWindow({
+    width: 800,
+    height: 600,
+    webPreferences: {
+      preload: path.join(__dirname, 'preload.js'),
+      contextIsolation: true,
+    },
+  });
+
+  mainWindow.loadFile('index.html');
+}
+
+app.whenReady().then(() => {
+  createWindow();
+});
+
+// API client
+const API_KEY = 'your-api-key-here';
+const API_URL = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
+
+// Handle file upload
+ipcMain.handle('upload-file', async (event, options) => {
+  try {
+    const { filePath, description, message, password, reference, activeDays } = options;
+    
+    // Get file info
+    const fileName = path.basename(filePath);
+    const fileStats = fs.statSync(filePath);
+    const fileSize = fileStats.size;
+    
+    // Step 1: Initialize upload
+    const initResponse = await axios.post(`${API_URL}/upload/init`, {
+      original_file_name: fileName,
+      file_size: fileSize,
+    }, {
+      headers: { 'X-Api-Key': API_KEY }
+    });
+    
+    const uploadId = initResponse.data.upload_id;
+    
+    // Step 2: Upload chunks
+    const chunkSize = 1024 * 1024; // 1MB chunks
+    const totalChunks = Math.ceil(fileSize / chunkSize);
+    const fileBuffer = fs.readFileSync(filePath);
+    
+    for (let i = 0; i < totalChunks; i++) {
+      const start = i * chunkSize;
+      const end = Math.min(fileSize, start + chunkSize);
+      const chunkBuffer = fileBuffer.slice(start, end);
+      
+      const formData = new FormData();
+      formData.append('upload_id', uploadId);
+      formData.append('chunk_index', i);
+      formData.append('total_chunks', totalChunks);
+      formData.append('chunk', chunkBuffer, { filename: 'chunk' });
+      
+      await axios.post(`${API_URL}/upload/chunk`, formData, {
+        headers: {
+          'X-Api-Key': API_KEY,
+          ...formData.getHeaders(),
+        }
+      });
+      
+      // Update progress
+      mainWindow.webContents.send('upload-progress', {
+        progress: Math.round((i + 1) / totalChunks * 100)
+      });
+    }
+    
+    // Step 3: Finalize upload
+    const activeToDate = new Date();
+    activeToDate.setDate(activeToDate.getDate() + parseInt(activeDays));
+    const activeTo = activeToDate.toISOString().slice(0, 19).replace('T', ' ');
+    
+    const finalizeResponse = await axios.post(`${API_URL}/upload/finalize`, {
+      upload_id: uploadId,
+      total_chunks: totalChunks,
+      original_file_name: fileName,
+      description,
+      message,
+      password,
+      reference,
+      active_to: activeTo,
+    }, {
+      headers: { 'X-Api-Key': API_KEY }
+    });
+    
+    return finalizeResponse.data;
+  } catch (error) {
+    console.error('Upload error:', error);
+    throw new Error(error.response?.data?.message || error.message);
+  }
+});
+
+// preload.js
+const { contextBridge, ipcRenderer } = require('electron');
+
+contextBridge.exposeInMainWorld('api', {
+  uploadFile: (options) => ipcRenderer.invoke('upload-file', options),
+  onUploadProgress: (callback) => {
+    ipcRenderer.on('upload-progress', (event, data) => callback(data));
+  }
+});
+
+// index.html
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>QDR File Uploader</title>
+  <style>
+    body {
+      font-family: Arial, sans-serif;
+      padding: 20px;
+    }
+    .form-group {
+      margin-bottom: 15px;
+    }
+    label {
+      display: block;
+      margin-bottom: 5px;
+    }
+    input, textarea {
+      width: 100%;
+      padding: 8px;
+      box-sizing: border-box;
+    }
+    .progress {
+      width: 100%;
+      height: 20px;
+      background-color: #f3f3f3;
+      margin-top: 10px;
+      display: none;
+    }
+    .progress-bar {
+      height: 100%;
+      background-color: #4CAF50;
+      width: 0%;
+      transition: width 0.3s;
+    }
+    #result {
+      margin-top: 20px;
+      padding: 10px;
+      border: 1px solid #ddd;
+      border-radius: 5px;
+      display: none;
+    }
+  </style>
+</head>
+<body>
+  <h1>QDR File Uploader</h1>
+  
+  <form id="upload-form">
+    <div class="form-group">
+      <label for="file-path">File Path:</label>
+      <input type="file" id="file-path" required>
+    </div>
+    
+    <div class="form-group">
+      <label for="description">Description:</label>
+      <input type="text" id="description" required>
+    </div>
+    
+    <div class="form-group">
+      <label for="message">Message:</label>
+      <textarea id="message"></textarea>
+    </div>
+    
+    <div class="form-group">
+      <label for="password">Password:</label>
+      <input type="password" id="password" required>
+    </div>
+    
+    <div class="form-group">
+      <label for="reference">Reference:</label>
+      <input type="text" id="reference">
+    </div>
+    
+    <div class="form-group">
+      <label for="active-days">Active for (days):</label>
+      <input type="number" id="active-days" value="30" min="1" max="365">
+    </div>
+    
+    <button type="submit">Upload</button>
+  </form>
+  
+  <div class="progress" id="progress-container">
+    <div class="progress-bar" id="progress-bar"></div>
+  </div>
+  
+  <div id="result"></div>
+  
+  <script>
+    document.getElementById('upload-form').addEventListener('submit', async function(e) {
+      e.preventDefault();
+      
+      const fileInput = document.getElementById('file-path');
+      if (!fileInput.files.length) {
+        alert('Please select a file');
+        return;
+      }
+      
+      const description = document.getElementById('description').value;
+      const message = document.getElementById('message').value;
+      const password = document.getElementById('password').value;
+      const reference = document.getElementById('reference').value;
+      const activeDays = document.getElementById('active-days').value;
+      
+      // Show progress
+      const progressContainer = document.getElementById('progress-container');
+      const progressBar = document.getElementById('progress-bar');
+      const resultDiv = document.getElementById('result');
+      
+      progressContainer.style.display = 'block';
+      progressBar.style.width = '0%';
+      resultDiv.style.display = 'none';
+      
+      // Disable form
+      const form = document.getElementById('upload-form');
+      form.querySelectorAll('input, textarea, button').forEach(el => {
+        el.disabled = true;
+      });
+      
+      try {
+        const result = await window.api.uploadFile({
+          filePath: fileInput.files[0].path,
+          description,
+          message,
+          password,
+          reference,
+          activeDays
+        });
+        
+        // Display result
+        resultDiv.innerHTML = `
+          <h2>File Uploaded Successfully!</h2>
+          <p>Media ID: ${result.media_id}</p>
+          <p>Download Link: <a href="${result.permalink}" target="_blank">${result.permalink}</a></p>
+          <p>Shortcode: <code>${result.shortcode}</code></p>
+        `;
+        resultDiv.style.display = 'block';
+      } catch (error) {
+        resultDiv.innerHTML = `<p style="color: red;">Error: ${error.message}</p>`;
+        resultDiv.style.display = 'block';
+      } finally {
+        // Re-enable form
+        form.querySelectorAll('input, textarea, button').forEach(el => {
+          el.disabled = false;
+        });
+      }
+    });
+    
+    // Update progress bar
+    window.api.onUploadProgress(function(data) {
+      document.getElementById('progress-bar').style.width = data.progress + '%';
+    });
+  </script>
+</body>
+</html>
+```
+
+## CRM Systems
+
+### Salesforce Integration
+
+You can integrate the plugin with Salesforce using Apex code to upload files and store the reference in Salesforce records.
+
+```java
+public class QDRFileUploaderController {
+    // API Configuration
+    private static final String API_URL = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
+    private static final String API_KEY = 'your-api-key-here';
+    
+    // Upload a file to QDR Temporary Shared Storage
+    @AuraEnabled
+    public static Map<String, String> uploadFile(String fileName, String fileContent, String description, 
+                                              String message, String password, String reference, Integer activeDays) {
+        try {
+            Blob fileBlob = EncodingUtil.base64Decode(fileContent);
+            Integer fileSize = fileBlob.size();
+            
+            // Step 1: Initialize upload
+            HttpRequest initRequest = new HttpRequest();
+            initRequest.setEndpoint(API_URL + '/upload/init');
+            initRequest.setMethod('POST');
+            initRequest.setHeader('X-Api-Key', API_KEY);
+            initRequest.setHeader('Content-Type', 'application/json');
+            
+            Map<String, Object> initParams = new Map<String, Object>();
+            initParams.put('original_file_name', fileName);
+            initParams.put('file_size', fileSize);
+            
+            initRequest.setBody(JSON.serialize(initParams));
+            
+            Http http = new Http();
+            HttpResponse initResponse = http.send(initRequest);
+            
+            if (initResponse.getStatusCode() != 200) {
+                throw new AuraHandledException('Failed to initialize upload: ' + initResponse.getBody());
+            }
+            
+            Map<String, Object> initResult = (Map<String, Object>) JSON.deserializeUntyped(initResponse.getBody());
+            String uploadId = (String) initResult.get('upload_id');
+            
+            // Step 2: Upload chunks
+            Integer chunkSize = 1024 * 1024; // 1MB chunks
+            Integer totalChunks = Math.ceil(fileSize / (Double) chunkSize).intValue();
+            
+            for (Integer i = 0; i < totalChunks; i++) {
+                Integer start = i * chunkSize;
+                Integer end = Math.min(fileSize, start + chunkSize);
+                
+                // Create chunk
+                Blob chunkBlob = fileBlob.substring(start, end);
+                String chunkBase64 = EncodingUtil.base64Encode(chunkBlob);
+                
+                // Upload chunk
+                HttpRequest chunkRequest = new HttpRequest();
+                chunkRequest.setEndpoint(API_URL + '/upload/chunk');
+                chunkRequest.setMethod('POST');
+                chunkRequest.setHeader('X-Api-Key', API_KEY);
+                
+                String boundary = '---------------------------' + String.valueOf(DateTime.now().getTime());
+                chunkRequest.setHeader('Content-Type', 'multipart/form-data; boundary=' + boundary);
+                
+                String body = '';
+                body += '--' + boundary + '\r\n';
+                body += 'Content-Disposition: form-data; name="upload_id"\r\n\r\n';
+                body += uploadId + '\r\n';
+                
+                body += '--' + boundary + '\r\n';
+                body += 'Content-Disposition: form-data; name="chunk_index"\r\n\r\n';
+                body += String.valueOf(i) + '\r\n';
+                
+                body += '--' + boundary + '\r\n';
+                body += 'Content-Disposition: form-data; name="total_chunks"\r\n\r\n';
+                body += String.valueOf(totalChunks) + '\r\n';
+                
+                body += '--' + boundary + '\r\n';
+                body += 'Content-Disposition: form-data; name="chunk"; filename="chunk"\r\n';
+                body += 'Content-Type: application/octet-stream\r\n\r\n';
+                
+                String bodyEnd = '\r\n--' + boundary + '--\r\n';
+                
+                Blob bodyBlob = Blob.valueOf(body);
+                Blob bodyEndBlob = Blob.valueOf(bodyEnd);
+                
+                // Join the blobs
+                Blob fullBody = Blob.valueOf('');
+                fullBody = Blob.valueOf(EncodingUtil.base64Encode(bodyBlob) + 
+                                       EncodingUtil.base64Encode(chunkBlob) + 
+                                       EncodingUtil.base64Encode(bodyEndBlob));
+                
+                chunkRequest.setBodyAsBlob(fullBody);
+                
+                HttpResponse chunkResponse = http.send(chunkRequest);
+                
+                if (chunkResponse.getStatusCode() != 200) {
+                    throw new AuraHandledException('Failed to upload chunk ' + i + ': ' + chunkResponse.getBody());
+                }
+            }
+            
+            // Step 3: Finalize upload
+            HttpRequest finalizeRequest = new HttpRequest();
+            finalizeRequest.setEndpoint(API_URL + '/upload/finalize');
+            finalizeRequest.setMethod('POST');
+            finalizeRequest.setHeader('X-Api-Key', API_KEY);
+            finalizeRequest.setHeader('Content-Type', 'application/json');
+            
+            // Set expiration date
+            Datetime activeToDate = Datetime.now();
+            activeToDate = activeToDate.addDays(activeDays);
+            String activeTo = activeToDate.format('yyyy-MM-dd HH:mm:ss');
+            
+            Map<String, Object> finalizeParams = new Map<String, Object>();
+            finalizeParams.put('upload_id', uploadId);
+            finalizeParams.put('total_chunks', totalChunks);
+            finalizeParams.put('original_file_name', fileName);
+            finalizeParams.put('description', description);
+            finalizeParams.put('message', message);
+            finalizeParams.put('password', password);
+            finalizeParams.put('reference', reference);
+            finalizeParams.put('active_to', activeTo);
+            
+            finalizeRequest.setBody(JSON.serialize(finalizeParams));
+            
+            HttpResponse finalizeResponse = http.send(finalizeRequest);
+            
+            if (finalizeResponse.getStatusCode() != 201) {
+                throw new AuraHandledException('Failed to finalize upload: ' + finalizeResponse.getBody());
+            }
+            
+            Map<String, Object> finalResult = (Map<String, Object>) JSON.deserializeUntyped(finalizeResponse.getBody());
+            
+            Map<String, String> result = new Map<String, String>();
+            result.put('mediaId', (String) finalResult.get('media_id'));
+            result.put('permalink', (String) finalResult.get('permalink'));
+            result.put('shortcode', (String) finalResult.get('shortcode'));
+            
+            return result;
+        } catch (Exception e) {
+            throw new AuraHandledException('Error uploading file: ' + e.getMessage());
+        }
+    }
+    
+    // Save shared file info to a Salesforce record
+    @AuraEnabled
+    public static void saveSharedFileInfo(Id recordId, String objectName, String mediaId, String permalink) {
+        try {
+            // Create a custom object to store the shared file info
+            SObject sharedFile = Schema.getGlobalDescribe().get('Shared_File__c').newSObject();
+            sharedFile.put('Media_ID__c', mediaId);
+            sharedFile.put('Permalink__c', permalink);
+            sharedFile.put('Related_Record__c', recordId);
+            sharedFile.put('Object_Type__c', objectName);
+            
+            insert sharedFile;
+        } catch (Exception e) {
+            throw new AuraHandledException('Error saving shared file info: ' + e.getMessage());
+        }
+    }
+}
+```
+
+## Document Management Systems
+
+### Integration with DMS Using PHP
+
+```php
+<?php
+/**
+ * QDR Temporary Shared Storage - Document Management System Integration
+ * 
+ * This script demonstrates how to integrate the QDR Temporary Shared Storage plugin
+ * with a Document Management System. It monitors a folder for new files, uploads them
+ * to the WordPress site via the REST API, and stores the media information in a database.
+ */
+
+// Configuration
+$config = [
+    'api_key' => 'your-api-key-here',
+    'api_url' => 'https://your-wordpress-site.com/wp-json/qdr-tss/v1',
+    'watch_folder' => '/path/to/watch/folder',
+    'processed_folder' => '/path/to/processed/folder',
+    'log_file' => '/path/to/log/file.log',
+    'db_host' => 'localhost',
+    'db_name' => 'dms_database',
+    'db_user' => 'db_user',
+    'db_pass' => 'db_password',
+];
+
+// Initialize database connection
+$db = new mysqli($config['db_host'], $config['db_user'], $config['db_pass'], $config['db_name']);
+if ($db->connect_error) {
+    log_message("Database connection failed: " . $db->connect_error);
+    exit;
+}
+
+// Create the shared_files table if it doesn't exist
+$db->query("
+    CREATE TABLE IF NOT EXISTS shared_files (
+        id INT AUTO_INCREMENT PRIMARY KEY,
+        file_path VARCHAR(255) NOT NULL,
+        original_file_name VARCHAR(255) NOT NULL,
+        media_id VARCHAR(255) NOT NULL,
+        permalink TEXT NOT NULL,
+        shortcode TEXT NOT NULL,
+        password VARCHAR(255) NOT NULL,
+        reference VARCHAR(255),
+        upload_date DATETIME DEFAULT CURRENT_TIMESTAMP,
+        expiry_date DATETIME NOT NULL,
+        document_type VARCHAR(50),
+        document_id VARCHAR(50),
+        notes TEXT
+    )
+");
+
+// Get all files in the watch folder
+$files = glob($config['watch_folder'] . '/*.*');
+
+foreach ($files as $file_path) {
+    $file_name = basename($file_path);
+    log_message("Processing file: " . $file_name);
+    
+    // Get metadata from the file name
+    // Assuming file names follow a pattern like "DOCTYPE_DOCID_REFERENCE.pdf"
+    $file_parts = explode('_', pathinfo($file_name, PATHINFO_FILENAME));
+    $document_type = isset($file_parts[0]) ? $file_parts[0] : '';
+    $document_id = isset($file_parts[1]) ? $file_parts[1] : '';
+    $reference = isset($file_parts[2]) ? $file_parts[2] : '';
+    
+    // Generate a random password
+    $password = generate_random_password();
+    
+    try {
+        // Upload the file
+        $result = upload_file(
+            $file_path,
+            "Document: $document_type - $document_id",
+            "This file was automatically uploaded by the DMS integration.",
+            $password,
+            $reference
+        );
+        
+        // Store the result in the database
+        $stmt = $db->prepare("INSERT INTO shared_files (
+            file_path, original_file_name, media_id, permalink, shortcode, 
+            password, reference, expiry_date, document_type, document_id
+        ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)");
+        
+        $expiry_date = date('Y-m-d H:i:s', strtotime('+30 days'));
+        
+        $stmt->bind_param(
+            'ssssssssss',
+            $file_path,
+            $file_name,
+            $result['media_id'],
+            $result['permalink'],
+            $result['shortcode'],
+            $password,
+            $reference,
+            $expiry_date,
+            $document_type,
+            $document_id
+        );
+        
+        $stmt->execute();
+        
+        if ($stmt->affected_rows > 0) {
+            log_message("File uploaded and recorded in the database.");
+            
+            // Move the file to the processed folder
+            $processed_path = $config['processed_folder'] . '/' . $file_name;
+            rename($file_path, $processed_path);
+            
+            log_message("File moved to processed folder.");
+        } else {
+            log_message("Failed to record file in the database.");
+        }
+        
+        $stmt->close();
+    } catch (Exception $e) {
+        log_message("Error: " . $e->getMessage());
+    }
+}
+
+$db->close();
+
+/**
+ * Upload a file using the QDR Temporary Shared Storage REST API
+ */
+function upload_file($file_path, $description, $message, $password, $reference) {
+    global $config;
+    
+    // File information
+    $file_name = basename($file_path);
+    $file_size = filesize($file_path);
+    $chunk_size = 1024 * 1024; // 1MB chunks
+    $total_chunks = ceil($file_size / $chunk_size);
+    
+    // Step 1: Initialize upload
+    $ch = curl_init($config['api_url'] . '/upload/init');
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+    curl_setopt($ch, CURLOPT_POST, true);
+    curl_setopt($ch, CURLOPT_HTTPHEADER, [
+        'X-Api-Key: ' . $config['api_key'],
+        'Content-Type: application/json'
+    ]);
+    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
+        'original_file_name' => $file_name,
+        'file_size' => $file_size
+    ]));
+    
+    $response = curl_exec($ch);
+    curl_close($ch);
+    
+    $init_result = json_decode($response, true);
+    if (!isset($init_result['upload_id'])) {
+        throw new Exception('Failed to initialize upload: ' . $response);
+    }
+    
+    $upload_id = $init_result['upload_id'];
+    
+    // Step 2: Upload chunks
+    $file = fopen($file_path, 'rb');
+    
+    for ($i = 0; $i < $total_chunks; $i++) {
+        // Create a temporary file for the chunk
+        $chunk_file = tempnam(sys_get_temp_dir(), 'chunk');
+        $chunk_handle = fopen($chunk_file, 'wb');
+        
+        // Read chunk from original file
+        $chunk_data = fread($file, $chunk_size);
+        fwrite($chunk_handle, $chunk_data);
+        fclose($chunk_handle);
+        
+        // Upload the chunk
+        $ch = curl_init($config['api_url'] . '/upload/chunk');
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_POST, true);
+        curl_setopt($ch, CURLOPT_HTTPHEADER, [
+            'X-Api-Key: ' . $config['api_key']
+        ]);
+        
+        $post_data = [
+            'upload_id' => $upload_id,
+            'chunk_index' => $i,
+            'total_chunks' => $total_chunks,
+            'chunk' => new CURLFile($chunk_file, 'application/octet-stream', 'chunk')
+        ];
+        
+        curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
+        $response = curl_exec($ch);
+        curl_close($ch);
+        
+        // Delete temporary chunk file
+        unlink($chunk_file);
+        
+        $chunk_result = json_decode($response, true);
+        if (!isset($chunk_result['status']) || $chunk_result['status'] !== 'chunk_uploaded') {
+            throw new Exception('Failed to upload chunk ' . $i . ': ' . $response);
+        }
+        
+        log_message("Uploaded chunk " . ($i + 1) . " of " . $total_chunks);
+    }
+    
+    fclose($file);
+    
+    // Step 3: Finalize upload
+    $ch = curl_init($config['api_url'] . '/upload/finalize');
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+    curl_setopt($ch, CURLOPT_POST, true);
+    curl_setopt($ch, CURLOPT_HTTPHEADER, [
+        'X-Api-Key: ' . $config['api_key'],
+        'Content-Type: application/json'
+    ]);
+    
+    // Set expiration to 30 days from now
+    $active_to = date('Y-m-d H:i:s', strtotime('+30 days'));
+    
+    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
+        'upload_id' => $upload_id,
+        'total_chunks' => $total_chunks,
+        'original_file_name' => $file_name,
+        'description' => $description,
+        'message' => $message,
+        'password' => $password,
+        'reference' => $reference,
+        'active_to' => $active_to
+    ]));
+    
+    $response = curl_exec($ch);
+    curl_close($ch);
+    
+    $final_result = json_decode($response, true);
+    if (!isset($final_result['media_id'])) {
+        throw new Exception('Failed to finalize upload: ' . $response);
+    }
+    
+    return $final_result;
+}
+
+/**
+ * Generate a random password
+ */
+function generate_random_password($length = 12) {
+    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+';
+    $password = '';
+    
+    for ($i = 0; $i < $length; $i++) {
+        $password .= $chars[rand(0, strlen($chars) - 1)];
+    }
+    
+    return $password;
+}
+
+/**
+ * Log a message to the log file
+ */
+function log_message($message) {
+    global $config;
+    
+    $date = date('Y-m-d H:i:s');
+    $log_message = "[$date] $message\n";
+    
+    file_put_contents($config['log_file'], $log_message, FILE_APPEND);
+    echo $log_message;
+}
+```
+
+## Email Systems
+
+### Email Integration with PHPMailer
+
+```php
+<?php
+/**
+ * QDR Temporary Shared Storage - Email Integration
+ * 
+ * This script demonstrates how to send an email with a link to download a file
+ * from the QDR Temporary Shared Storage plugin.
+ */
+
+// Require PHPMailer
+use PHPMailer\PHPMailer\PHPMailer;
+use PHPMailer\PHPMailer\Exception;
+
+require 'vendor/autoload.php';
+
+/**
+ * Upload a file to QDR Temporary Shared Storage and send an email with the link
+ *
+ * @param string $file_path      Path to the file to upload
+ * @param string $description    Description of the file
+ * @param string $message        Custom message
+ * @param string $recipient_email Email address of the recipient
+ * @param string $recipient_name  Name of the recipient
+ * @param string $email_subject   Subject of the email
+ * @param string $email_body      Body of the email (HTML)
+ * @return array Upload result
+ */
+function send_file_by_email($file_path, $description, $message, $recipient_email, $recipient_name, $email_subject, $email_body) {
+    // Generate a random password
+    $password = generate_random_password();
+    
+    // Generate a unique reference code
+    $reference = uniqid('EMAIL-');
+    
+    // Upload the file
+    $result = upload_file($file_path, $description, $message, $password, $reference);
+    
+    // Prepare email content
+    $download_link = $result['permalink'];
+    $media_id = $result['media_id'];
+    
+    // Replace placeholders in the email body
+    $email_body = str_replace('{{DOWNLOAD_LINK}}', $download_link, $email_body);
+    $email_body = str_replace('{{PASSWORD}}', $password, $email_body);
+    $email_body = str_replace('{{REFERENCE}}', $reference, $email_body);
+    $email_body = str_replace('{{FILE_NAME}}', basename($file_path), $email_body);
+    $email_body = str_replace('{{EXPIRY_DATE}}', date('Y-m-d', strtotime('+30 days')), $email_body);
+    
+    // Send the email
+    send_email($recipient_email, $recipient_name, $email_subject, $email_body);
+    
+    return $result;
+}
+
+/**
+ * Send an email using PHPMailer
+ */
+function send_email($to_email, $to_name, $subject, $body) {
+    $mail = new PHPMailer(true);
+    
+    try {
+        // Server settings
+        $mail->isSMTP();
+        $mail->Host       = 'smtp.example.com';
+        $mail->SMTPAuth   = true;
+        $mail->Username   = 'user@example.com';
+        $mail->Password   = 'password';
+        $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
+        $mail->Port       = 587;
+        
+        // Recipients
+        $mail->setFrom('from@example.com', 'Your Company');
+        $mail->addAddress($to_email, $to_name);
+        $mail->addReplyTo('info@example.com', 'Information');
+        
+        // Content
+        $mail->isHTML(true);
+        $mail->Subject = $subject;
+        $mail->Body    = $body;
+        $mail->AltBody = strip_tags($body);
+        
+        $mail->send();
+        return true;
+    } catch (Exception $e) {
+        throw new Exception('Message could not be sent. Mailer Error: ' . $mail->ErrorInfo);
+    }
+}
+
+/**
+ * Upload a file using the QDR Temporary Shared Storage REST API
+ */
+function upload_file($file_path, $description, $message, $password, $reference) {
+    $api_key = 'your-api-key-here';
+    $api_url = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
+    
+    // Implement the file upload logic (same as in previous examples)
+    // ...
+}
+
+/**
+ * Generate a random password
+ */
+function generate_random_password($length = 12) {
+    $chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+';
+    $password = '';
+    
+    for ($i = 0; $i < $length; $i++) {
+        $password .= $chars[rand(0, strlen($chars) - 1)];
+    }
+    
+    return $password;
+}
+
+// Example usage
+try {
+    $file_path = '/path/to/document.pdf';
+    $description = 'Important Document';
+    $message = 'Please review this document and provide feedback.';
+    $recipient_email = 'client@example.com';
+    $recipient_name = 'John Doe';
+    $email_subject = 'Important Document for Review';
+    
+    $email_body = '
+    <html>
+    <body>
+        <h2>Important Document</h2>
+        <p>Dear {{RECIPIENT_NAME}},</p>
+        <p>We have shared an important document with you for review.</p>
+        <p>You can download the document using the following information:</p>
+        <ul>
+            <li><strong>Download Link:</strong> <a href="{{DOWNLOAD_LINK}}">{{FILE_NAME}}</a></li>
+            <li><strong>Password:</strong> {{PASSWORD}}</li>
+            <li><strong>Reference:</strong> {{REFERENCE}}</li>
+        </ul>
+        <p>The link will be active until {{EXPIRY_DATE}}.</p>
+        <p>If you have any questions, please contact us.</p>
+        <p>Best regards,<br>Your Company</p>
+    </body>
+    </html>
+    ';
+    
+    $email_body = str_replace('{{RECIPIENT_NAME}}', $recipient_name, $email_body);
+    
+    $result = send_file_by_email($file_path, $description, $message, $recipient_email, $recipient_name, $email_subject, $email_body);
+    
+    echo "File uploaded and email sent successfully!\n";
+    echo "Media ID: " . $result['media_id'] . "\n";
+    echo "Permalink: " . $result['permalink'] . "\n";
+} catch (Exception $e) {
+    echo "Error: " . $e->getMessage() . "\n";
+}
+```
+
+## Workflow Automation
+
+### Zapier Integration
+
+You can integrate the plugin with Zapier to automate workflows. Here's an example Zapier integration script:
+
+```javascript
+// Zapier Code for QDR Temporary Shared Storage Integration
+
+// When a new file is added to Google Drive (Trigger)
+const inputData = {
+  fileUrl: inputData.fileUrl,
+  fileName: inputData.fileName,
+  fileId: inputData.fileId,
+  mimeType: inputData.mimeType
+};
+
+// Download the file content
+const fileResponse = await fetch(inputData.fileUrl, {
+  method: 'GET',
+  headers: {
+    'Authorization': `Bearer ${bundle.authData.access_token}`
+  }
+});
+
+// Get file as buffer
+const fileBuffer = await fileResponse.buffer();
+
+// Upload file to QDR Temporary Shared Storage
+const API_KEY = 'your-api-key-here';
+const API_URL = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
+
+// Step 1: Initialize upload
+const initResponse = await fetch(`${API_URL}/upload/init`, {
+  method: 'POST',
+  headers: {
+    'X-Api-Key': API_KEY,
+    'Content-Type': 'application/json'
+  },
+  body: JSON.stringify({
+    original_file_name: inputData.fileName,
+    file_size: fileBuffer.length
+  })
+});
+
+const initData = await initResponse.json();
+
+if (!initData.upload_id) {
+  throw new Error('Failed to initialize upload');
+}
+
+const uploadId = initData.upload_id;
+
+// Step 2: Upload chunks
+const chunkSize = 1024 * 1024; // 1MB chunks
+const totalChunks = Math.ceil(fileBuffer.length / chunkSize);
+
+for (let i = 0; i < totalChunks; i++) {
+  const start = i * chunkSize;
+  const end = Math.min(fileBuffer.length, start + chunkSize);
+  const chunk = fileBuffer.slice(start, end);
+  
+  const formData = new FormData();
+  formData.append('upload_id', uploadId);
+  formData.append('chunk_index', i);
+  formData.append('total_chunks', totalChunks);
+  formData.append('chunk', new Blob([chunk], { type: 'application/octet-stream' }), 'chunk');
+  
+  const chunkResponse = await fetch(`${API_URL}/upload/chunk`, {
+    method: 'POST',
+    headers: {
+      'X-Api-Key': API_KEY
+    },
+    body: formData
+  });
+  
+  const chunkData = await chunkResponse.json();
+  
+  if (!chunkData.status || chunkData.status !== 'chunk_uploaded') {
+    throw new Error(`Failed to upload chunk ${i}`);
+  }
+}
+
+// Step 3: Finalize upload
+const description = `File uploaded from Google Drive: ${inputData.fileName}`;
+const message = 'This file was automatically uploaded via Zapier.';
+const password = generatePassword();
+const reference = `ZAPIER-${Date.now()}`;
+const activeTo = new Date();
+activeTo.setDate(activeTo.getDate() + 30); // 30 days from now
+
+const finalizeResponse = await fetch(`${API_URL}/upload/finalize`, {
+  method: 'POST',
+  headers: {
+    'X-Api-Key': API_KEY,
+    'Content-Type': 'application/json'
+  },
+  body: JSON.stringify({
+    upload_id: uploadId,
+    total_chunks: totalChunks,
+    original_file_name: inputData.fileName,
+    description,
+    message,
+    password,
+    reference,
+    active_to: activeTo.toISOString().slice(0, 19).replace('T', ' ')
+  })
+});
+
+const finalizeData = await finalizeResponse.json();
+
+if (!finalizeData.media_id) {
+  throw new Error('Failed to finalize upload');
+}
+
+// Generate password function
+function generatePassword(length = 12) {
+  const chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()-_=+';
+  let password = '';
+  
+  for (let i = 0; i < length; i++) {
+    password += chars.charAt(Math.floor(Math.random() * chars.length));
+  }
+  
+  return password;
+}
+
+// Return data for next Zapier step
+return {
+  media_id: finalizeData.media_id,
+  permalink: finalizeData.permalink,
+  shortcode: finalizeData.shortcode,
+  password: password,
+  reference: reference,
+  expiry_date: activeTo.toISOString().slice(0, 10)
+};
+```
+
+## Conclusion
+
+The QDR Temporary Shared Storage plugin's REST API enables integration with a wide variety of applications and systems. By following the examples in this guide, you can easily incorporate secure file sharing capabilities into your existing workflows and applications.
+
+For further assistance or custom integration solutions, please contact the plugin author or refer to the API documentation in the plugin's readme file.

+ 241 - 0
qdr-temporary-shared-storage/@documentation/plugin-architecture-diagram.mermaid

@@ -0,0 +1,241 @@
+```mermaid
+classDiagram
+    class QDR_TSS {
+        -plugin_name: string
+        -version: string
+        -loader: QDR_TSS_Loader
+        +__construct()
+        +define_constants()
+        -load_dependencies()
+        -set_locale()
+        -define_admin_hooks()
+        -define_public_hooks()
+        -define_rest_api_hooks()
+        -define_cron_hooks()
+        +run()
+        +get_plugin_name(): string
+        +get_loader(): QDR_TSS_Loader
+        +get_version(): string
+    }
+
+    class QDR_TSS_Loader {
+        -actions: array
+        -filters: array
+        +__construct()
+        +add_action(hook, component, callback, priority, accepted_args)
+        +add_filter(hook, component, callback, priority, accepted_args)
+        -add(hooks, hook, component, callback, priority, accepted_args): array
+        +run()
+    }
+
+    class QDR_TSS_i18n {
+        +load_plugin_textdomain()
+    }
+
+    class QDR_TSS_Activator {
+        +activate(): static
+    }
+
+    class QDR_TSS_Deactivator {
+        +deactivate(): static
+    }
+
+    class QDR_TSS_DB_Manager {
+        -$_instance: QDR_TSS_DB_Manager
+        -table_name: string
+        +instance(): QDR_TSS_DB_Manager
+        +__construct()
+        +create_table(): bool
+        +get_item(id): object
+        +get_item_by_media_id(media_id): object
+        +get_items(args): array
+        +insert_item(data): int|false
+        +update_item(id, data): bool
+        +update_item_by_media_id(media_id, data): bool
+        +delete_item(id): bool
+        +delete_item_by_media_id(media_id): bool
+        +get_expired_items(): array
+        +delete_expired_items(): int
+        +increment_download_count(id): bool
+        +get_total_storage(): int
+        +would_exceed_storage_limit(file_size, max_size): bool
+        -get_column_formats(data): array
+    }
+
+    class QDR_TSS_File_Manager {
+        -$_instance: QDR_TSS_File_Manager
+        +instance(): QDR_TSS_File_Manager
+        +__construct()
+        +init_file_system(): bool
+        +create_dated_directory(date): string|false
+        +init_chunked_upload(): string|false
+        +store_chunk(upload_id, chunk_index, file): bool
+        +check_all_chunks(upload_id, total_chunks): bool
+        +finalize_chunked_upload(upload_id, total_chunks, original_filename): array|false
+        -combine_chunks(upload_dir, total_chunks, file_path): bool
+        -cleanup_chunks(upload_dir): bool
+        +delete_file(file_path): bool
+        +get_file_size(file_path): int|false
+        +cleanup_empty_directories(directory): void
+    }
+
+    class QDR_TSS_Settings {
+        -$_instance: QDR_TSS_Settings
+        -option_name: string
+        -defaults: array
+        -settings: array
+        +instance(): QDR_TSS_Settings
+        +__construct()
+        +load_settings(): void
+        +get_settings(): array
+        +get_setting(key, default): mixed
+        +update_settings(settings): bool
+        +update_setting(key, value): bool
+        +generate_api_key(): string
+        +validate_api_key(api_key): bool
+        +format_bytes(bytes, precision): string
+    }
+
+    class QDR_TSS_Cron_Manager {
+        -plugin_name: string
+        -version: string
+        +__construct(plugin_name, version)
+        +purge_expired_files(): int
+        +manual_purge_expired_files(): int
+        +schedule_purge_event(): void
+        +unschedule_purge_event(): void
+    }
+
+    class QDR_TSS_Admin {
+        -plugin_name: string
+        -version: string
+        +__construct(plugin_name, version)
+        +enqueue_styles(): void
+        +enqueue_scripts(): void
+        +add_admin_menu(): void
+        +render_media_page(): void
+        +render_settings_page(): void
+    }
+
+    class QDR_TSS_Media_Page {
+        -plugin_name: string
+        -version: string
+        -db_manager: QDR_TSS_DB_Manager
+        -file_manager: QDR_TSS_File_Manager
+        +__construct(plugin_name, version)
+        +ajax_get_items(): void
+        +ajax_edit_item(): void
+        +ajax_delete_item(): void
+        +ajax_get_item_details(): void
+    }
+
+    class QDR_TSS_Settings_Page {
+        -plugin_name: string
+        -version: string
+        -settings: QDR_TSS_Settings
+        -cron_manager: QDR_TSS_Cron_Manager
+        +__construct(plugin_name, version)
+        +ajax_save_settings(): void
+        +ajax_purge_expired(): void
+    }
+
+    class QDR_TSS_REST_Controller {
+        -plugin_name: string
+        -version: string
+        -settings: QDR_TSS_Settings
+        +__construct(plugin_name, version)
+        +register_routes(): void
+        +check_api_key(result): WP_Error|null
+        +check_rest_permission(): bool
+        -get_all_headers(): array
+    }
+
+    class QDR_TSS_Media_Controller {
+        -plugin_name: string
+        -version: string
+        -namespace: string
+        -base: string
+        -db_manager: QDR_TSS_DB_Manager
+        -file_manager: QDR_TSS_File_Manager
+        +__construct(plugin_name, version)
+        +register_routes(): void
+        +check_permission(): bool
+        +get_items(request): WP_REST_Response|WP_Error
+        +get_item(request): WP_REST_Response|WP_Error
+        +update_item(request): WP_REST_Response|WP_Error
+        +delete_item(request): WP_REST_Response|WP_Error
+    }
+
+    class QDR_TSS_Upload_Controller {
+        -plugin_name: string
+        -version: string
+        -namespace: string
+        -db_manager: QDR_TSS_DB_Manager
+        -file_manager: QDR_TSS_File_Manager
+        -settings: QDR_TSS_Settings
+        +__construct(plugin_name, version)
+        +register_routes(): void
+        +check_permission(): bool
+        +init_chunked_upload(request): WP_REST_Response|WP_Error
+        +upload_chunk(request): WP_REST_Response|WP_Error
+        +finalize_chunked_upload(request): WP_REST_Response|WP_Error
+    }
+
+    class QDR_TSS_Public {
+        -plugin_name: string
+        -version: string
+        +__construct(plugin_name, version)
+        +enqueue_styles(): void
+        +enqueue_scripts(): void
+        +create_download_page(): void
+    }
+
+    class QDR_TSS_Shortcode {
+        -plugin_name: string
+        -version: string
+        -download: QDR_TSS_Download
+        +__construct(plugin_name, version)
+        +register_shortcodes(): void
+        +download_shortcode(atts): string
+    }
+
+    class QDR_TSS_Download {
+        -plugin_name: string
+        -version: string
+        -db_manager: QDR_TSS_DB_Manager
+        -file_manager: QDR_TSS_File_Manager
+        +__construct(plugin_name, version)
+        +process_download_request(atts): string
+        -render_download_form(media_id): string
+    }
+
+    %% Relationships
+    QDR_TSS --> QDR_TSS_Loader : uses
+    QDR_TSS --> QDR_TSS_i18n : uses
+    QDR_TSS --> QDR_TSS_Admin : uses
+    QDR_TSS --> QDR_TSS_Public : uses
+    QDR_TSS --> QDR_TSS_REST_Controller : uses
+    QDR_TSS_Activator --> QDR_TSS_DB_Manager : uses
+    QDR_TSS_Activator --> QDR_TSS_File_Manager : uses
+    QDR_TSS_Activator --> QDR_TSS_Settings : uses
+    QDR_TSS_Admin --> QDR_TSS_Media_Page : uses
+    QDR_TSS_Admin --> QDR_TSS_Settings_Page : uses
+    QDR_TSS_Media_Page --> QDR_TSS_DB_Manager : uses
+    QDR_TSS_Media_Page --> QDR_TSS_File_Manager : uses
+    QDR_TSS_Settings_Page --> QDR_TSS_Settings : uses
+    QDR_TSS_Settings_Page --> QDR_TSS_Cron_Manager : uses
+    QDR_TSS_REST_Controller --> QDR_TSS_Media_Controller : uses
+    QDR_TSS_REST_Controller --> QDR_TSS_Upload_Controller : uses
+    QDR_TSS_REST_Controller --> QDR_TSS_Settings : uses
+    QDR_TSS_Media_Controller --> QDR_TSS_DB_Manager : uses
+    QDR_TSS_Media_Controller --> QDR_TSS_File_Manager : uses
+    QDR_TSS_Upload_Controller --> QDR_TSS_DB_Manager : uses
+    QDR_TSS_Upload_Controller --> QDR_TSS_File_Manager : uses
+    QDR_TSS_Upload_Controller --> QDR_TSS_Settings : uses
+    QDR_TSS_Public --> QDR_TSS_Shortcode : uses
+    QDR_TSS_Shortcode --> QDR_TSS_Download : uses
+    QDR_TSS_Download --> QDR_TSS_DB_Manager : uses
+    QDR_TSS_Download --> QDR_TSS_File_Manager : uses
+    QDR_TSS_Cron_Manager --> QDR_TSS_DB_Manager : uses
+    QDR_TSS_Cron_Manager --> QDR_TSS_File_Manager : uses
+```

Разлика између датотеке није приказан због своје велике величине
+ 0 - 0
qdr-temporary-shared-storage/@documentation/plugin-architecture-diagram.svg


+ 239 - 0
qdr-temporary-shared-storage/@documentation/plugin-structure.md

@@ -0,0 +1,239 @@
+# QDR Temporary Shared Storage - Plugin Structure
+
+## Directory Structure
+
+```
+qdr-temporary-shared-storage/
+├── qdr-temporary-shared-storage.php       # Main plugin file (bootstrap)
+├── uninstall.php                          # Clean uninstallation
+│
+├── includes/                              # Core functionality
+│   ├── class-qdr-tss.php                  # Core plugin class
+│   ├── class-qdr-tss-activator.php        # Plugin activation
+│   ├── class-qdr-tss-deactivator.php      # Plugin deactivation
+│   ├── class-qdr-tss-db-manager.php       # Database operations
+│   ├── class-qdr-tss-file-manager.php     # File operations
+│   ├── class-qdr-tss-settings.php         # Settings management
+│   ├── class-qdr-tss-cron-manager.php     # Scheduled tasks
+│   ├── class-qdr-tss-loader.php           # Hook loader (registers all hooks)
+│   ├── class-qdr-tss-i18n.php             # Internationalization
+│   └── functions.php                      # Helper functions
+│
+├── 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
+│   ├── partials/                          # Admin UI templates
+│   │   ├── media-page.php                 # Media listing page template
+│   │   └── settings-page.php              # Settings page template
+│   ├── js/
+│   │   └── admin.js                       # Admin JavaScript
+│   └── css/
+│       └── admin.css                      # Admin styles
+│
+├── public/                                # Public-facing functionality
+│   ├── class-qdr-tss-public.php           # Public controller
+│   ├── class-qdr-tss-shortcode.php        # Shortcode functionality
+│   ├── class-qdr-tss-download.php         # Download functionality
+│   ├── js/
+│   │   └── public.js                      # Public JavaScript
+│   └── css/
+│       └── public.css                     # Public styles
+│
+├── rest-api/                              # REST API functionality
+│   ├── class-qdr-tss-rest-controller.php  # REST API base controller
+│   ├── class-qdr-tss-media-controller.php # Media endpoints
+│   └── class-qdr-tss-upload-controller.php# Upload endpoints
+│
+├── languages/                             # Translation files
+│   ├── qdr-temporary-shared-storage.pot   # Translation template
+│   └── qdr-temporary-shared-storage-cs_CZ.po # Czech translation
+│
+└── docs/                                  # Documentation
+    ├── integration-guide.md               # Integration guide
+    ├── plugin-structure.md                # Plugin structure documentation
+    ├── rest-api-docs.md                   # REST API documentation
+    └── testing-guide.md                   # Testing guide
+```
+
+## Class Responsibilities
+
+### Core Classes
+
+#### QDR_TSS (Main Plugin Class)
+- Initialize the plugin
+- Load dependencies
+- Register hooks
+- 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
+
+#### QDR_TSS_Deactivator
+- Handle plugin deactivation
+- Clear scheduled hooks
+
+#### QDR_TSS_Loader
+- Register actions and filters
+- Orchestrate all hooks for the plugin
+
+#### QDR_TSS_i18n
+- Load text domain
+- Enable internationalization
+
+#### QDR_TSS_DB_Manager
+- Create/update custom tables
+- Database migrations
+- Database queries for media items
+- 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
+- Get/set plugin settings
+- Settings validation
+- Default settings
+- API key management
+
+#### QDR_TSS_Cron_Manager
+- Register cron schedules
+- Handle expired files cleanup
+- Schedule checks
+
+### Admin Classes
+
+#### QDR_TSS_Admin
+- Register admin menu items
+- Enqueue admin scripts/styles
+- Handle AJAX requests
+- Initialize admin hooks
+
+#### QDR_TSS_Media_Page
+- Render Media Shared Storage page
+- Handle media listing
+- Edit/delete functionality
+- AJAX handlers for media operations
+
+#### QDR_TSS_Settings_Page
+- Render Settings page
+- Save settings
+- AJAX handlers for settings operations
+- Storage management
+- Handle purge expired files
+
+### Public Classes
+
+#### QDR_TSS_Public
+- Register public scripts/styles
+- Public hooks
+- Create download page
+
+#### QDR_TSS_Shortcode
+- Register shortcodes
+- Render download forms
+
+#### QDR_TSS_Download
+- Handle download requests
+- Verify passwords and references
+- Process file download
+- Update download stats
+
+### REST API Classes
+
+#### QDR_TSS_REST_Controller
+- Base REST API functionality
+- Authentication
+- Permissions
+
+#### QDR_TSS_Media_Controller
+- GET/PUT/DELETE media endpoints
+- List media collection
+- Media properties management
+
+#### QDR_TSS_Upload_Controller
+- Chunked upload endpoints
+- File validation
+- Upload initialization
+- Chunk handling
+- Upload finalization
+
+## File Dependencies
+
+Here's a list of the main file dependencies:
+
+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:
+   - class-qdr-tss-loader.php
+   - class-qdr-tss-i18n.php
+   - class-qdr-tss-db-manager.php
+   - class-qdr-tss-file-manager.php
+   - 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/css/admin.css
+
+4. **class-qdr-tss-public.php** - Loads:
+   - public/js/public.js
+   - public/css/public.css
+
+## 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)
+)
+```

+ 595 - 0
qdr-temporary-shared-storage/@documentation/rest-api-docs.md

@@ -0,0 +1,595 @@
+# QDR Temporary Shared Storage - REST API Documentation
+
+## Introduction
+
+The QDR Temporary Shared Storage plugin provides a comprehensive REST API for uploading, managing, and downloading temporary shared files. This document outlines the available endpoints, authentication requirements, and provides examples in both PHP and JavaScript.
+
+## Authentication
+
+All API requests require an API key sent in the `X-Api-Key` HTTP header. You can generate or view your API key in the plugin settings page at "Settings > Shared Storage".
+
+## API Base URL
+
+The base URL for all API endpoints is:
+
+```
+https://your-wordpress-site.com/wp-json/qdr-tss/v1/
+```
+
+## Endpoints
+
+### 1. Initialize Chunked Upload
+
+Prepares the server for a chunked upload.
+
+- **Endpoint**: `/upload/init`
+- **Method**: `POST`
+- **Parameters**:
+  - `original_file_name`: The original name of the file being uploaded
+  - `file_size`: The total size of the file in bytes
+
+- **Response**:
+  ```json
+  {
+    "upload_id": "5f8d7a6b9c3e",
+    "status": "initialized"
+  }
+  ```
+
+### 2. Upload Chunk
+
+Uploads a single chunk of the file.
+
+- **Endpoint**: `/upload/chunk`
+- **Method**: `POST`
+- **Parameters**:
+  - `upload_id`: The upload ID received from the initialization
+  - `chunk_index`: The index of the current chunk (starting from 0)
+  - `total_chunks`: The total number of chunks
+  - `chunk`: The chunk file data (multipart/form-data)
+
+- **Response**:
+  ```json
+  {
+    "upload_id": "5f8d7a6b9c3e",
+    "chunk_index": 0,
+    "status": "chunk_uploaded"
+  }
+  ```
+
+### 3. Finalize Upload
+
+Combines all chunks and creates the file record.
+
+- **Endpoint**: `/upload/finalize`
+- **Method**: `POST`
+- **Parameters**:
+  - `upload_id`: The upload ID received from the initialization
+  - `total_chunks`: The total number of chunks
+  - `original_file_name`: The original name of the file
+  - `description`: Description of the file
+  - `message`: Custom message (optional)
+  - `active_from`: Date from which permalink will be active (optional, defaults to current time)
+  - `active_to`: Date until which permalink will be active (optional, defaults to 30 days from now)
+  - `password`: Password for download protection
+  - `reference`: Custom reference value (optional)
+
+- **Response**:
+  ```json
+  {
+    "media_id": "1621234567",
+    "permalink": "https://your-wordpress-site.com/shared-file-download/1621234567",
+    "shortcode": "[qdr_tss_download id=\"1621234567\"]"
+  }
+  ```
+
+### 4. Get File Details
+
+Retrieves details about a specific file.
+
+- **Endpoint**: `/media/{media_id}`
+- **Method**: `GET`
+
+- **Response**:
+  ```json
+  {
+    "id": 1,
+    "media_id": "1621234567",
+    "original_file_name": "example.pdf",
+    "description": "Example document",
+    "message": "Please review this document",
+    "active_from": "2025-05-19 15:30:00",
+    "active_to": "2025-06-18 15:30:00",
+    "reference": "REF123",
+    "upload_date": "2025-05-19 15:30:00",
+    "download_count": 5,
+    "last_download": "2025-05-20 10:45:30",
+    "file_size": 1048576
+  }
+  ```
+
+### 5. Update File
+
+Updates properties of a specific file.
+
+- **Endpoint**: `/media/{media_id}`
+- **Method**: `PUT`
+- **Parameters**:
+  - `message`: Custom message (optional)
+  - `active_from`: Date from which permalink will be active (optional)
+  - `active_to`: Date until which permalink will be active (optional)
+  - `reference`: Custom reference value (optional)
+  - `description`: File description (optional)
+
+- **Response**: Updated file details (same format as Get File Details)
+
+### 6. Delete File
+
+Deletes a specific file.
+
+- **Endpoint**: `/media/{media_id}`
+- **Method**: `DELETE`
+
+- **Response**:
+  ```json
+  {
+    "deleted": true,
+    "media_id": "1621234567"
+  }
+  ```
+
+### 7. List Files
+
+Retrieves a list of files with pagination and filtering.
+
+- **Endpoint**: `/media`
+- **Method**: `GET`
+- **Parameters**:
+  - `page`: Page number (optional, default: 1)
+  - `per_page`: Items per page (optional, default: 10)
+  - `orderby`: Field to sort by (optional, default: "upload_date")
+  - `order`: Sort order (optional, default: "DESC")
+  - `original_file_name`: Filter by file name (optional)
+  - `reference`: Filter by reference (optional)
+
+- **Response**:
+  ```json
+  [
+    {
+      "id": 1,
+      "media_id": "1621234567",
+      "original_file_name": "example.pdf",
+      "description": "Example document",
+      "message": "Please review this document",
+      "active_from": "2025-05-19 15:30:00",
+      "active_to": "2025-06-18 15:30:00",
+      "reference": "REF123",
+      "upload_date": "2025-05-19 15:30:00",
+      "download_count": 5,
+      "last_download": "2025-05-20 10:45:30",
+      "file_size": 1048576
+    },
+    ...
+  ]
+  ```
+
+## Code Examples
+
+### PHP Example: Complete File Upload
+
+```php
+<?php
+/**
+ * Example of uploading a file using chunked upload
+ */
+function uploadSharedFile($file_path, $description, $password, $reference = '') {
+    $api_key = 'your-api-key-here';
+    $api_url = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
+    
+    // File information
+    $file_name = basename($file_path);
+    $file_size = filesize($file_path);
+    $chunk_size = 1024 * 1024; // 1MB chunks
+    $total_chunks = ceil($file_size / $chunk_size);
+    
+    // Step 1: Initialize upload
+    $ch = curl_init($api_url . '/upload/init');
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+    curl_setopt($ch, CURLOPT_POST, true);
+    curl_setopt($ch, CURLOPT_HTTPHEADER, [
+        'X-Api-Key: ' . $api_key,
+        'Content-Type: application/json'
+    ]);
+    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
+        'original_file_name' => $file_name,
+        'file_size' => $file_size
+    ]));
+    
+    $response = curl_exec($ch);
+    curl_close($ch);
+    
+    $init_result = json_decode($response, true);
+    if (!isset($init_result['upload_id'])) {
+        throw new Exception('Failed to initialize upload: ' . $response);
+    }
+    
+    $upload_id = $init_result['upload_id'];
+    
+    // Step 2: Upload chunks
+    $file = fopen($file_path, 'rb');
+    
+    for ($i = 0; $i < $total_chunks; $i++) {
+        // Create a temporary file for the chunk
+        $chunk_file = tempnam(sys_get_temp_dir(), 'chunk');
+        $chunk_handle = fopen($chunk_file, 'wb');
+        
+        // Read chunk from original file
+        $chunk_data = fread($file, $chunk_size);
+        fwrite($chunk_handle, $chunk_data);
+        fclose($chunk_handle);
+        
+        // Upload the chunk
+        $ch = curl_init($api_url . '/upload/chunk');
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_POST, true);
+        curl_setopt($ch, CURLOPT_HTTPHEADER, [
+            'X-Api-Key: ' . $api_key
+        ]);
+        
+        $post_data = [
+            'upload_id' => $upload_id,
+            'chunk_index' => $i,
+            'total_chunks' => $total_chunks,
+            'chunk' => new CURLFile($chunk_file, 'application/octet-stream', 'chunk')
+        ];
+        
+        curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
+        $response = curl_exec($ch);
+        curl_close($ch);
+        
+        // Delete temporary chunk file
+        unlink($chunk_file);
+        
+        $chunk_result = json_decode($response, true);
+        if (!isset($chunk_result['status']) || $chunk_result['status'] !== 'chunk_uploaded') {
+            throw new Exception('Failed to upload chunk ' . $i . ': ' . $response);
+        }
+        
+        // Display progress
+        echo "Uploaded chunk " . ($i + 1) . " of " . $total_chunks . "\n";
+    }
+    
+    fclose($file);
+    
+    // Step 3: Finalize upload
+    $ch = curl_init($api_url . '/upload/finalize');
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+    curl_setopt($ch, CURLOPT_POST, true);
+    curl_setopt($ch, CURLOPT_HTTPHEADER, [
+        'X-Api-Key: ' . $api_key,
+        'Content-Type: application/json'
+    ]);
+    
+    // Set expiration to 30 days from now
+    $active_to = date('Y-m-d H:i:s', strtotime('+30 days'));
+    
+    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
+        'upload_id' => $upload_id,
+        'total_chunks' => $total_chunks,
+        'original_file_name' => $file_name,
+        'description' => $description,
+        'message' => 'Uploaded via API example',
+        'password' => $password,
+        'reference' => $reference,
+        'active_to' => $active_to
+    ]));
+    
+    $response = curl_exec($ch);
+    curl_close($ch);
+    
+    $final_result = json_decode($response, true);
+    if (!isset($final_result['media_id'])) {
+        throw new Exception('Failed to finalize upload: ' . $response);
+    }
+    
+    return $final_result;
+}
+
+// Example usage
+try {
+    $result = uploadSharedFile(
+        '/path/to/your/file.pdf',
+        'Example document uploaded via API',
+        'secure_password',
+        'REF123'
+    );
+    
+    echo "File uploaded successfully!\n";
+    echo "Media ID: " . $result['media_id'] . "\n";
+    echo "Permalink: " . $result['permalink'] . "\n";
+    echo "Shortcode: " . $result['shortcode'] . "\n";
+} catch (Exception $e) {
+    echo "Error: " . $e->getMessage() . "\n";
+}
+```
+
+### JavaScript Example: Chunked File Upload
+
+```javascript
+/**
+ * Uploads a file using chunked upload
+ * @param {File} file The file to upload
+ * @param {string} description File description
+ * @param {string} password Password for download protection
+ * @param {string} reference Optional reference
+ * @param {function} progressCallback Callback for upload progress
+ * @returns {Promise} Promise resolving to the upload result
+ */
+async function uploadSharedFile(file, description, password, reference = '', progressCallback = null) {
+    const apiKey = 'your-api-key-here';
+    const apiUrl = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
+    
+    const chunkSize = 1024 * 1024; // 1MB chunks
+    const totalChunks = Math.ceil(file.size / chunkSize);
+    
+    // Step 1: Initialize upload
+    const initResponse = await fetch(`${apiUrl}/upload/init`, {
+        method: 'POST',
+        headers: {
+            'X-Api-Key': apiKey,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({
+            original_file_name: file.name,
+            file_size: file.size
+        })
+    });
+    
+    const initResult = await initResponse.json();
+    if (!initResult.upload_id) {
+        throw new Error('Failed to initialize upload');
+    }
+    
+    const uploadId = initResult.upload_id;
+    
+    // Step 2: Upload chunks
+    for (let i = 0; i < totalChunks; i++) {
+        const start = i * chunkSize;
+        const end = Math.min(file.size, start + chunkSize);
+        const chunk = file.slice(start, end);
+        
+        const formData = new FormData();
+        formData.append('upload_id', uploadId);
+        formData.append('chunk_index', i);
+        formData.append('total_chunks', totalChunks);
+        formData.append('chunk', chunk);
+        
+        const chunkResponse = await fetch(`${apiUrl}/upload/chunk`, {
+            method: 'POST',
+            headers: {
+                'X-Api-Key': apiKey
+            },
+            body: formData
+        });
+        
+        const chunkResult = await chunkResponse.json();
+        if (!chunkResult.status || chunkResult.status !== 'chunk_uploaded') {
+            throw new Error(`Failed to upload chunk ${i}`);
+        }
+        
+        // Update progress
+        if (progressCallback) {
+            progressCallback((i + 1) / totalChunks * 100);
+        }
+    }
+    
+    // Step 3: Finalize upload
+    const activeToDate = new Date();
+    activeToDate.setDate(activeToDate.getDate() + 30); // 30 days from now
+    const activeTo = activeToDate.toISOString().slice(0, 19).replace('T', ' ');
+    
+    const finalizeResponse = await fetch(`${apiUrl}/upload/finalize`, {
+        method: 'POST',
+        headers: {
+            'X-Api-Key': apiKey,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({
+            upload_id: uploadId,
+            total_chunks: totalChunks,
+            original_file_name: file.name,
+            description: description,
+            message: 'Uploaded via JavaScript API example',
+            password: password,
+            reference: reference,
+            active_to: activeTo
+        })
+    });
+    
+    const finalResult = await finalizeResponse.json();
+    if (!finalResult.media_id) {
+        throw new Error('Failed to finalize upload');
+    }
+    
+    return finalResult;
+}
+
+// Example usage with HTML file input
+document.getElementById('file-input').addEventListener('change', async (event) => {
+    const file = event.target.files[0];
+    if (!file) return;
+    
+    const progressBar = document.getElementById('progress-bar');
+    
+    try {
+        const result = await uploadSharedFile(
+            file,
+            'Example document uploaded via JavaScript',
+            'secure_password',
+            'REF123',
+            (progress) => {
+                progressBar.style.width = `${progress}%`;
+            }
+        );
+        
+        console.log('File uploaded successfully!');
+        console.log(`Media ID: ${result.media_id}`);
+        console.log(`Permalink: ${result.permalink}`);
+        console.log(`Shortcode: ${result.shortcode}`);
+    } catch (error) {
+        console.error('Error:', error.message);
+    }
+});
+```
+
+### PHP Example: Get File List
+
+```php
+<?php
+/**
+ * Example of retrieving a list of files
+ */
+function getSharedFiles($page = 1, $perPage = 10, $orderBy = 'upload_date', $order = 'DESC', $fileName = '', $reference = '') {
+    $api_key = 'your-api-key-here';
+    $api_url = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
+    
+    // Build query parameters
+    $query_params = http_build_query([
+        'page' => $page,
+        'per_page' => $perPage,
+        'orderby' => $orderBy,
+        'order' => $order,
+        'original_file_name' => $fileName,
+        'reference' => $reference
+    ]);
+    
+    $ch = curl_init($api_url . '/media?' . $query_params);
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+    curl_setopt($ch, CURLOPT_HTTPHEADER, [
+        'X-Api-Key: ' . $api_key
+    ]);
+    
+    $response = curl_exec($ch);
+    $header_size = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
+    $header = substr($response, 0, $header_size);
+    $body = substr($response, $header_size);
+    
+    // Get pagination headers
+    $total = 0;
+    $total_pages = 0;
+    
+    foreach (explode("\r\n", $header) as $line) {
+        if (strpos($line, 'X-WP-Total:') === 0) {
+            $total = intval(trim(str_replace('X-WP-Total:', '', $line)));
+        } else if (strpos($line, 'X-WP-TotalPages:') === 0) {
+            $total_pages = intval(trim(str_replace('X-WP-TotalPages:', '', $line)));
+        }
+    }
+    
+    curl_close($ch);
+    
+    $files = json_decode($body, true);
+    
+    return [
+        'files' => $files,
+        'total' => $total,
+        'total_pages' => $total_pages
+    ];
+}
+
+// Example usage
+$result = getSharedFiles(1, 10, 'upload_date', 'DESC', '', 'REF123');
+
+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";
+}
+```
+
+### JavaScript Example: Update File Properties
+
+```javascript
+/**
+ * Updates file properties
+ * @param {string} mediaId The media ID
+ * @param {object} properties Properties to update
+ * @returns {Promise} Promise resolving to the updated file
+ */
+async function updateSharedFile(mediaId, 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}`, {
+        method: 'PUT',
+        headers: {
+            'X-Api-Key': apiKey,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify(properties)
+    });
+    
+    const result = await response.json();
+    if (!result.media_id) {
+        throw new Error('Failed to update file');
+    }
+    
+    return result;
+}
+
+// Example usage
+async function exampleUpdateFile() {
+    try {
+        const updatedFile = await updateSharedFile('1621234567', {
+            message: 'Updated message',
+            active_to: '2025-07-19 15:30:00',
+            reference: 'UPDATED-REF'
+        });
+        
+        console.log('File updated successfully!');
+        console.log(updatedFile);
+    } catch (error) {
+        console.error('Error:', error.message);
+    }
+}
+
+exampleUpdateFile();
+```
+
+## Error Handling
+
+All API endpoints return standard HTTP status codes:
+
+- `200 OK`: Request succeeded
+- `201 Created`: Resource created successfully
+- `400 Bad Request`: Invalid request parameters
+- `401 Unauthorized`: Missing API key
+- `403 Forbidden`: Invalid API key
+- `404 Not Found`: Resource not found
+- `500 Internal Server Error`: Server error
+
+Error responses include a message explaining the error:
+
+```json
+{
+  "code": "qdr_tss_not_found",
+  "message": "Media not found.",
+  "data": {
+    "status": 404
+  }
+}
+```
+
+## Rate Limiting
+
+There are currently no rate limits on the API, but excessive usage may be throttled in the future.
+
+## 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
+

+ 496 - 0
qdr-temporary-shared-storage/@documentation/testing-guide.md

@@ -0,0 +1,496 @@
+# 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.
+
+## Manual Testing Procedures
+
+### 1. Installation and Activation Testing
+
+- [ ] Upload plugin to a test WordPress site
+- [ ] Activate the plugin
+- [ ] Verify that activation creates:
+  - [ ] The custom database table
+  - [ ] The required upload directories
+  - [ ] The download page
+  - [ ] The default plugin settings
+
+### 2. Admin Interface Testing
+
+#### 2.1 Settings Page
+
+- [ ] Navigate to Settings > Shared Storage
+- [ ] Verify all settings fields are displayed correctly
+- [ ] Test generating a new API key
+- [ ] Change settings values and save
+- [ ] Refresh page and verify settings were saved
+- [ ] Test the "Purge Expired Files" button
+
+#### 2.2 Media Page
+
+- [ ] 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)
+- [ ] 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
+
+### 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 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
+
+### 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)
+
+## 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**:
+   ```bash
+   composer require --dev phpunit/phpunit
+   ```
+
+2. **Install WordPress Test Library**:
+   ```bash
+   bash bin/install-wp-tests.sh wordpress_test root '' localhost latest
+   ```
+
+3. **Create a PHPUnit Configuration File** (`phpunit.xml`):
+   ```xml
+   <phpunit
+       bootstrap="tests/bootstrap.php"
+       backupGlobals="false"
+       colors="true"
+       convertErrorsToExceptions="true"
+       convertNoticesToExceptions="true"
+       convertWarningsToExceptions="true"
+   >
+       <testsuites>
+           <testsuite name="QDR Temporary Shared Storage Test Suite">
+               <directory prefix="test-" suffix=".php">./tests/</directory>
+           </testsuite>
+       </testsuites>
+   </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
+
+Create a file `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() {
+        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 inserting an item
+     */
+    public function test_insert_item() {
+        $data = array(
+            'media_id' => 123456789,
+            'original_file_name' => 'test.pdf',
+            'description' => 'Test description',
+            'message' => 'Test message',
+            'active_from' => current_time('mysql'),
+            'active_to' => date('Y-m-d H:i:s', strtotime('+30 days')),
+            'password' => wp_hash_password('test_password'),
+            'reference' => 'TEST123',
+            'file_path' => 'test/path/test.pdf',
+            'file_size' => 1024
+        );
+        
+        $item_id = $this->db_manager->insert_item($data);
+        
+        $this->assertNotFalse($item_id);
+        $this->assertGreaterThan(0, $item_id);
+        
+        // Get the item and verify data
+        $item = $this->db_manager->get_item($item_id);
+        
+        $this->assertEquals($data['media_id'], $item->media_id);
+        $this->assertEquals($data['original_file_name'], $item->original_file_name);
+        $this->assertEquals($data['description'], $item->description);
+    }
+
+    // Additional tests for update, delete, get operations...
+}
+```
+
+#### File Manager Tests
+
+Create a file `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() {
+        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();
+        
+        $this->assertTrue($result);
+        $this->assertTrue(file_exists(QDR_TSS_UPLOADS_DIR));
+        $this->assertTrue(file_exists(QDR_TSS_UPLOADS_DIR . 'tmp/'));
+        $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);
+        
+        $this->assertTrue(file_exists($dir));
+    }
+
+    // Additional tests for chunked uploads, file deletion, etc...
+}
+```
+
+#### REST API Tests
+
+Create a file `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() {
+        parent::setUp();
+        global $wp_rest_server;
+        $this->server = $wp_rest_server = new WP_REST_Server;
+        do_action('rest_api_init');
+        
+        // Set up API key
+        $settings = QDR_TSS_Settings::instance();
+        $this->api_key = $settings->generate_api_key();
+        $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);
+        
+        $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);
+        $response = $this->server->dispatch($request);
+        
+        $this->assertEquals(200, $response->get_status());
+    }
+
+    // Additional tests for other API endpoints...
+}
+```
+
+### Running the Tests
+
+Run the tests using the PHPUnit command:
+
+```bash
+./vendor/bin/phpunit
+```
+
+## Integration Testing with Client Applications
+
+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
+
+```php
+<?php
+// Define API credentials
+$api_url = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
+$api_key = 'your-api-key-here';
+
+// Test the API
+function test_api() {
+    global $api_url, $api_key;
+    
+    // Test initialization
+    $init_response = initialize_upload('test.txt', 1024);
+    if (!isset($init_response['upload_id'])) {
+        echo "FAIL: Initialize upload\n";
+        return;
+    }
+    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;
+    }
+    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;
+    }
+    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;
+    }
+    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;
+    }
+    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;
+    }
+    echo "PASS: Delete media\n";
+    
+    echo "All tests passed!\n";
+}
+
+// API helper functions
+function initialize_upload($filename, $filesize) {
+    global $api_url, $api_key;
+    
+    $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
+    ]));
+    
+    $response = curl_exec($ch);
+    curl_close($ch);
+    
+    return json_decode($response, true);
+}
+
+// Implement other API helper functions...
+
+// Run tests
+test_api();
+```
+
+## 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.
+
+### Test Scenarios
+
+1. **Large File Upload**: Test uploading a very large file (1GB+) and measure:
+   - Total upload time
+   - Memory usage
+   - CPU usage
+
+2. **Concurrent Uploads**: Test multiple clients uploading files simultaneously and measure:
+   - Success rate
+   - Average upload time
+   - Server resource usage
+
+3. **Storage Limit Testing**: Test behavior when approaching and exceeding storage limits.
+
+## Security Testing
+
+### Checklist
+
+1. **API Authentication**:
+   - [ ] Test API requests with missing API key
+   - [ ] Test API requests with invalid API key
+   - [ ] Test API requests with valid API key
+
+2. **File Access**:
+   - [ ] Verify direct access to uploaded files is blocked
+   - [ ] Verify download requires valid password
+   - [ ] Verify download respects active dates
+
+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
+
+4. **File Operations**:
+   - [ ] Test upload paths for directory traversal
+   - [ ] Test file type restrictions
+   - [ ] Test file size limits
+
+## Conclusion
+
+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.

+ 167 - 0
qdr-temporary-shared-storage/admin/class-qdr-tss-admin.php

@@ -0,0 +1,167 @@
+<?php
+/**
+ * The admin-specific functionality of the plugin.
+ *
+ * Defines the plugin name, version, and hooks for
+ * the admin area of the site.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+class QDR_TSS_Admin {
+
+    /**
+     * The ID of this plugin.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $plugin_name    The ID of this plugin.
+     */
+    private $plugin_name;
+
+    /**
+     * The version of this plugin.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $version    The current version of this plugin.
+     */
+    private $version;
+
+    /**
+     * Initialize the class and set its properties.
+     *
+     * @since    1.0.0
+     * @param    string    $plugin_name    The name of this plugin.
+     * @param    string    $version        The version of this plugin.
+     */
+    public function __construct($plugin_name, $version) {
+        $this->plugin_name = $plugin_name;
+        $this->version = $version;
+    }
+
+    /**
+     * Register the stylesheets for the admin area.
+     *
+     * @since    1.0.0
+     */
+    public function enqueue_styles() {
+        // Only load on plugin admin pages
+        $screen = get_current_screen();
+        if (strpos($screen->id, 'qdr-temporary-shared-storage') === false) {
+            return;
+        }
+        
+        wp_enqueue_style(
+            $this->plugin_name . '-admin',
+            plugin_dir_url(dirname(__FILE__)) . 'admin/css/admin.css',
+            array(),
+            $this->version,
+            'all'
+        );
+        
+        // Add jQuery UI styles for datepicker
+        wp_enqueue_style('jquery-ui-datepicker');
+    }
+
+    /**
+     * Register the JavaScript for the admin area.
+     *
+     * @since    1.0.0
+     */
+    public function enqueue_scripts() {
+        // Only load on plugin admin pages
+        $screen = get_current_screen();
+        if (strpos($screen->id, 'qdr-temporary-shared-storage') === false) {
+            return;
+        }
+        
+        wp_enqueue_script(
+            $this->plugin_name . '-admin',
+            plugin_dir_url(dirname(__FILE__)) . 'admin/js/admin.js',
+            array('jquery', 'jquery-ui-datepicker'),
+            $this->version,
+            true
+        );
+        
+        // Localize script with translations and data
+        wp_localize_script($this->plugin_name . '-admin', 'qdr_tss', array(
+            'ajaxurl' => admin_url('admin-ajax.php'),
+            'nonce' => wp_create_nonce('qdr_tss_nonce'),
+            'strings' => array(
+                'loading' => __('Loading...', 'qdr-temporary-shared-storage'),
+                'saving' => __('Saving...', 'qdr-temporary-shared-storage'),
+                'no_items' => __('No items', 'qdr-temporary-shared-storage'),
+                'no_items_found' => __('No items found.', 'qdr-temporary-shared-storage'),
+                'showing' => __('Showing', 'qdr-temporary-shared-storage'),
+                'of' => __('of', 'qdr-temporary-shared-storage'),
+                'first_page' => __('First Page', 'qdr-temporary-shared-storage'),
+                'previous_page' => __('Previous Page', 'qdr-temporary-shared-storage'),
+                'next_page' => __('Next Page', 'qdr-temporary-shared-storage'),
+                'last_page' => __('Last Page', 'qdr-temporary-shared-storage'),
+                'view_details' => __('View Details', 'qdr-temporary-shared-storage'),
+                'edit' => __('Edit', 'qdr-temporary-shared-storage'),
+                'delete' => __('Delete', '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'),
+                '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'),
+                'error_loading_item_details_try_again' => __('Error loading item details. Please try again.', 'qdr-temporary-shared-storage'),
+                'changes_saved' => __('Changes saved successfully.', 'qdr-temporary-shared-storage'),
+                'error_saving_changes' => __('Error saving changes.', 'qdr-temporary-shared-storage'),
+                'error_saving_changes_try_again' => __('Error saving changes. Please try again.', 'qdr-temporary-shared-storage'),
+                'file_deleted' => __('File deleted successfully.', 'qdr-temporary-shared-storage'),
+                'error_deleting_file' => __('Error deleting file.', 'qdr-temporary-shared-storage'),
+                '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'),
+            )
+        ));
+    }
+
+    /**
+     * Add admin menu items.
+     *
+     * @since    1.0.0
+     */
+    public function add_admin_menu() {
+        // Add Shared Storage page under Media menu
+        add_submenu_page(
+            'upload.php',
+            __('Shared Storage', 'qdr-temporary-shared-storage'),
+            __('Shared Storage', 'qdr-temporary-shared-storage'),
+            'manage_options',
+            'qdr-temporary-shared-storage',
+            array($this, 'render_media_page')
+        );
+        
+        // Add Settings page
+        add_submenu_page(
+            'options-general.php',
+            __('Shared Storage Settings', 'qdr-temporary-shared-storage'),
+            __('Shared Storage', 'qdr-temporary-shared-storage'),
+            'manage_options',
+            'qdr-temporary-shared-storage-settings',
+            array($this, 'render_settings_page')
+        );
+    }
+
+    /**
+     * Render the Media Shared Storage admin page.
+     *
+     * @since    1.0.0
+     */
+    public function render_media_page() {
+        require_once plugin_dir_path(dirname(__FILE__)) . 'admin/partials/media-page.php';
+    }
+
+    /**
+     * Render the Settings admin page.
+     *
+     * @since    1.0.0
+     */
+    public function render_settings_page() {
+        require_once plugin_dir_path(dirname(__FILE__)) . 'admin/partials/settings-page.php';
+    }
+}

+ 227 - 0
qdr-temporary-shared-storage/admin/class-qdr-tss-media-page.php

@@ -0,0 +1,227 @@
+<?php
+/**
+ * Media Page functionality of the plugin.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+class QDR_TSS_Media_Page {
+
+    /**
+     * The ID of this plugin.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $plugin_name    The ID of this plugin.
+     */
+    private $plugin_name;
+
+    /**
+     * The version of this plugin.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $version    The current version of this plugin.
+     */
+    private $version;
+
+    /**
+     * The database manager instance.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      QDR_TSS_DB_Manager    $db_manager    The database manager instance.
+     */
+    private $db_manager;
+
+    /**
+     * The file manager instance.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      QDR_TSS_File_Manager    $file_manager    The file manager instance.
+     */
+    private $file_manager;
+
+    /**
+     * Initialize the class and set its properties.
+     *
+     * @since    1.0.0
+     * @param    string    $plugin_name    The name of this plugin.
+     * @param    string    $version        The version of this plugin.
+     */
+    public function __construct($plugin_name, $version) {
+        $this->plugin_name = $plugin_name;
+        $this->version = $version;
+        $this->db_manager = QDR_TSS_DB_Manager::instance();
+        $this->file_manager = QDR_TSS_File_Manager::instance();
+    }
+
+    /**
+     * AJAX handler for getting items.
+     *
+     * @since    1.0.0
+     */
+    public function ajax_get_items() {
+        // 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')));
+        }
+        
+        // Parse parameters
+        $args = array(
+            'per_page' => isset($_POST['per_page']) ? intval($_POST['per_page']) : 10,
+            'page' => isset($_POST['page']) ? intval($_POST['page']) : 1,
+            'orderby' => isset($_POST['orderby']) ? sanitize_text_field($_POST['orderby']) : 'upload_date',
+            'order' => isset($_POST['order']) ? sanitize_text_field($_POST['order']) : 'DESC',
+            'original_file_name' => isset($_POST['original_file_name']) ? sanitize_text_field($_POST['original_file_name']) : '',
+            'reference' => isset($_POST['reference']) ? sanitize_text_field($_POST['reference']) : '',
+        );
+        
+        $result = $this->db_manager->get_items($args);
+        
+        wp_send_json_success($result);
+    }
+
+    /**
+     * AJAX handler for editing an item.
+     *
+     * @since    1.0.0
+     */
+    public function ajax_edit_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')));
+        }
+        
+        // Prepare update data
+        $data = array();
+        
+        if (isset($_POST['description'])) {
+            $data['description'] = sanitize_textarea_field($_POST['description']);
+        }
+        
+        if (isset($_POST['message'])) {
+            $data['message'] = sanitize_textarea_field($_POST['message']);
+        }
+        
+        if (isset($_POST['active_from'])) {
+            $data['active_from'] = sanitize_text_field($_POST['active_from']);
+        }
+        
+        if (isset($_POST['active_to'])) {
+            $data['active_to'] = sanitize_text_field($_POST['active_to']);
+        }
+        
+        if (isset($_POST['reference'])) {
+            $data['reference'] = sanitize_text_field($_POST['reference']);
+        }
+        
+        if (empty($data)) {
+            wp_send_json_error(array('message' => __('No changes to update.', 'qdr-temporary-shared-storage')));
+        }
+        
+        // Update in database
+        $result = $this->db_manager->update_item($id, $data);
+        
+        if ($result === false) {
+            wp_send_json_error(array('message' => __('Failed to update item.', 'qdr-temporary-shared-storage')));
+        }
+        
+        // Get updated record
+        $updated_item = $this->db_manager->get_item($id);
+        
+        wp_send_json_success(array(
+            'item' => $updated_item,
+            'message' => __('Item updated successfully.', 'qdr-temporary-shared-storage')
+        ));
+    }
+
+    /**
+     * AJAX handler for deleting an item.
+     *
+     * @since    1.0.0
+     */
+    public function ajax_delete_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')));
+        }
+        
+        // Delete file
+        $this->file_manager->delete_file($item->file_path);
+        
+        // Delete from database
+        $result = $this->db_manager->delete_item($id);
+        
+        if ($result === false) {
+            wp_send_json_error(array('message' => __('Failed to delete item.', 'qdr-temporary-shared-storage')));
+        }
+        
+        wp_send_json_success(array(
+            'message' => __('Item deleted successfully.', 'qdr-temporary-shared-storage')
+        ));
+    }
+
+    /**
+     * AJAX handler for getting item details.
+     *
+     * @since    1.0.0
+     */
+    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')));
+        }
+        
+        // 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')));
+        }
+        
+        // Add permalink and shortcode
+        $item->permalink = site_url('shared-file-download/' . $item->media_id);
+        $item->shortcode = '[qdr_tss_download id="' . $item->media_id . '"]';
+        
+        // Remove password from response
+        unset($item->password);
+        
+        wp_send_json_success(array(
+            'item' => $item
+        ));
+    }
+}

+ 138 - 0
qdr-temporary-shared-storage/admin/class-qdr-tss-settings-page.php

@@ -0,0 +1,138 @@
+<?php
+/**
+ * Settings Page functionality of the plugin.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+class QDR_TSS_Settings_Page {
+
+    /**
+     * The ID of this plugin.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $plugin_name    The ID of this plugin.
+     */
+    private $plugin_name;
+
+    /**
+     * The version of this plugin.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $version    The current version of this plugin.
+     */
+    private $version;
+
+    /**
+     * The settings instance.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      QDR_TSS_Settings    $settings    The settings instance.
+     */
+    private $settings;
+
+    /**
+     * The database manager instance.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      QDR_TSS_DB_Manager    $db_manager    The database manager instance.
+     */
+    private $db_manager;
+
+    /**
+     * The file manager instance.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      QDR_TSS_File_Manager    $file_manager    The file manager instance.
+     */
+    private $file_manager;
+
+    /**
+     * The cron manager instance.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      QDR_TSS_Cron_Manager    $cron_manager    The cron manager instance.
+     */
+    private $cron_manager;
+
+    /**
+     * Initialize the class and set its properties.
+     *
+     * @since    1.0.0
+     * @param    string    $plugin_name    The name of this plugin.
+     * @param    string    $version        The version of this plugin.
+     */
+    public function __construct($plugin_name, $version) {
+        $this->plugin_name = $plugin_name;
+        $this->version = $version;
+        $this->settings = QDR_TSS_Settings::instance();
+        $this->db_manager = QDR_TSS_DB_Manager::instance();
+        $this->file_manager = QDR_TSS_File_Manager::instance();
+        $this->cron_manager = new QDR_TSS_Cron_Manager($plugin_name, $version);
+    }
+
+    /**
+     * AJAX handler for saving settings.
+     *
+     * @since    1.0.0
+     */
+    public function ajax_save_settings() {
+        // 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')));
+        }
+        
+        // Get settings
+        $api_key = isset($_POST['api_key']) ? sanitize_text_field($_POST['api_key']) : '';
+        $media_category = isset($_POST['media_category']) ? sanitize_text_field($_POST['media_category']) : 'shared-storage';
+        $max_upload_size = isset($_POST['max_upload_size']) ? intval($_POST['max_upload_size']) : 1073741824; // 1GB default
+        
+        // Update settings
+        $this->settings->update_settings(array(
+            'api_key' => $api_key,
+            'media_category' => $media_category,
+            'max_upload_size' => $max_upload_size
+        ));
+        
+        wp_send_json_success(array(
+            'message' => __('Settings saved successfully.', 'qdr-temporary-shared-storage')
+        ));
+    }
+
+    /**
+     * AJAX handler for purging expired files.
+     *
+     * @since    1.0.0
+     */
+    public function ajax_purge_expired() {
+        // 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')));
+        }
+        
+        // Purge expired files
+        $purged_count = $this->cron_manager->manual_purge_expired_files();
+        
+        // Translators: %d is the number of purged files
+        $message = sprintf(
+            _n(
+                '%d expired file has been purged.',
+                '%d expired files have been purged.',
+                $purged_count,
+                'qdr-temporary-shared-storage'
+            ),
+            $purged_count
+        );
+        
+        wp_send_json_success(array(
+            'message' => $message,
+            'purged_count' => $purged_count
+        ));
+    }
+}

+ 63 - 32
qdr-temporary-shared-storage/assets/css/admin.css → qdr-temporary-shared-storage/admin/css/admin.css

@@ -21,7 +21,7 @@
 
 
 .qdr-tss-search input {
 .qdr-tss-search input {
     margin-right: 10px;
     margin-right: 10px;
-    min-width: 300px;
+    min-width: 200px;
 }
 }
 
 
 /* Table */
 /* Table */
@@ -72,6 +72,31 @@
     background-color: #f0f0f0;
     background-color: #f0f0f0;
 }
 }
 
 
+.qdr-tss-table th.sorted.asc .sorting-indicator:before {
+    content: "\f142";
+    display: inline-block;
+    font: normal 20px/1 dashicons;
+}
+
+.qdr-tss-table th.sorted.desc .sorting-indicator:before {
+    content: "\f140";
+    display: inline-block;
+    font: normal 20px/1 dashicons;
+}
+
+/* Status Indicators */
+.qdr-tss-status-active td {
+    background-color: #f0f7e6;
+}
+
+.qdr-tss-status-pending td {
+    background-color: #f5f5f5;
+}
+
+.qdr-tss-status-expired td {
+    background-color: #fef7f1;
+}
+
 /* Pagination */
 /* Pagination */
 .qdr-tss-pagination {
 .qdr-tss-pagination {
     display: flex;
     display: flex;
@@ -109,6 +134,10 @@
     box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
     box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
 }
 }
 
 
+.qdr-tss-modal-sm {
+    max-width: 400px;
+}
+
 .qdr-tss-modal-close {
 .qdr-tss-modal-close {
     color: #aaa;
     color: #aaa;
     float: right;
     float: right;
@@ -149,6 +178,18 @@
     margin-left: 10px;
     margin-left: 10px;
 }
 }
 
 
+/* Loading */
+.qdr-tss-loading {
+    display: flex;
+    align-items: center;
+}
+
+.qdr-tss-loading .spinner {
+    float: none;
+    margin-top: 0;
+    margin-right: 10px;
+}
+
 /* Settings page */
 /* Settings page */
 .qdr-tss-storage-bar {
 .qdr-tss-storage-bar {
     width: 100%;
     width: 100%;
@@ -164,43 +205,33 @@
     background-color: #2271b1;
     background-color: #2271b1;
 }
 }
 
 
-/* Download page */
-.qdr-tss-download-form {
-    max-width: 600px;
-    margin: 20px auto;
-    padding: 20px;
-    background-color: #f8f8f8;
-    border-radius: 5px;
-    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+/* Action Buttons */
+.qdr-tss-action-buttons {
+    display: flex;
+    justify-content: center;
 }
 }
 
 
-.qdr-tss-download-info {
-    max-width: 600px;
-    margin: 20px auto;
-    padding: 20px;
-    background-color: #fff;
-    border-radius: 5px;
-    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+.qdr-tss-action-buttons .button {
+    margin: 0 2px;
+    padding: 0 5px;
+    height: 30px;
 }
 }
 
 
-.qdr-tss-error {
-    color: #d63638;
-    margin: 20px 0;
-    padding: 10px;
-    background-color: #ffdddd;
-    border-left: 4px solid #d63638;
+.qdr-tss-action-buttons .dashicons {
+    font-size: 16px;
+    line-height: 30px;
 }
 }
 
 
-.qdr-tss-button {
-    background-color: #2271b1;
-    color: #fff;
-    border: none;
-    padding: 10px 15px;
-    border-radius: 3px;
-    cursor: pointer;
-    font-size: 14px;
+/* Details */
+.qdr-tss-details-content {
+    margin-bottom: 20px;
+}
+
+.qdr-tss-details-content table {
+    width: 100%;
 }
 }
 
 
-.qdr-tss-button:hover {
-    background-color: #135e96;
+.qdr-tss-details-content th {
+    width: 30%;
+    text-align: left;
 }
 }

+ 662 - 0
qdr-temporary-shared-storage/admin/js/admin.js

@@ -0,0 +1,662 @@
+/**
+ * Admin JavaScript for QDR Temporary Shared Storage plugin
+ *
+ * Handles all admin-side functionality including:
+ * - Media page (loading, pagination, sorting, filtering, editing, deleting)
+ * - Settings page (API key generation, settings saving, purging expired files)
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+
+(function($) {
+    'use strict';
+
+    // Media page functionality
+    var QDR_TSS_Media = {
+        currentPage: 1,
+        perPage: 10,
+        totalPages: 1,
+        currentOrderBy: 'upload_date',
+        currentOrder: 'DESC',
+        currentSearchFilename: '',
+        currentSearchReference: '',
+
+        /**
+         * Initialize the media page
+         */
+        init: function() {
+            // Load items on page load
+            this.loadItems();
+
+            // Initialize event handlers
+            this.initEventHandlers();
+
+            // Initialize datepicker
+            this.initDatepicker();
+        },
+
+        /**
+         * Initialize event handlers
+         */
+        initEventHandlers: function() {
+            var self = this;
+
+            // Handle sorting
+            $('.qdr-tss-table th.sortable').on('click', function() {
+                var column = $(this).data('sort');
+                
+                if (self.currentOrderBy === column) {
+                    self.currentOrder = self.currentOrder === 'ASC' ? 'DESC' : 'ASC';
+                } else {
+                    self.currentOrderBy = column;
+                    self.currentOrder = 'ASC';
+                }
+                
+                self.loadItems();
+            });
+            
+            // Handle search
+            $('#qdr-tss-search-button').on('click', function() {
+                self.currentSearchFilename = $('#qdr-tss-search-filename').val();
+                self.currentSearchReference = $('#qdr-tss-search-reference').val();
+                self.currentPage = 1;
+                self.loadItems();
+            });
+            
+            // Handle search reset
+            $('#qdr-tss-reset-search').on('click', function() {
+                $('#qdr-tss-search-filename').val('');
+                $('#qdr-tss-search-reference').val('');
+                self.currentSearchFilename = '';
+                self.currentSearchReference = '';
+                self.currentPage = 1;
+                self.loadItems();
+            });
+            
+            // Handle refresh button
+            $('#qdr-tss-refresh').on('click', function() {
+                self.loadItems();
+            });
+            
+            // Handle modal close
+            $('.qdr-tss-modal-close, .qdr-tss-modal-cancel').on('click', function() {
+                $('.qdr-tss-modal').hide();
+            });
+            
+            // Close modal when clicking outside
+            $(window).on('click', function(e) {
+                if ($(e.target).hasClass('qdr-tss-modal')) {
+                    $('.qdr-tss-modal').hide();
+                }
+            });
+            
+            // Handle copy shortcode
+            $(document).on('click', '.qdr-tss-copy-shortcode', function() {
+                var shortcode = $('#qdr-tss-details-shortcode code').text();
+                self.copyToClipboard(shortcode);
+                alert(qdr_tss.strings.shortcode_copied);
+            });
+            
+            // Handle copy permalink
+            $(document).on('click', '.qdr-tss-copy-permalink', function() {
+                var permalink = $('#qdr-tss-details-permalink a').attr('href');
+                self.copyToClipboard(permalink);
+                alert(qdr_tss.strings.permalink_copied);
+            });
+            
+            // Handle edit item form submission
+            $('#qdr-tss-edit-form').on('submit', function(e) {
+                e.preventDefault();
+                self.updateItem();
+            });
+            
+            // Handle confirm delete
+            $(document).on('click', '.qdr-tss-confirm-delete-button', function() {
+                var id = $('#qdr-tss-delete-id').val();
+                self.deleteItem(id);
+            });
+        },
+
+        /**
+         * Initialize datepicker
+         */
+        initDatepicker: function() {
+            $(document).on('focus', '.qdr-tss-datepicker', function() {
+                $(this).datepicker({
+                    dateFormat: 'yy-mm-dd',
+                    changeMonth: true,
+                    changeYear: true
+                });
+            });
+        },
+
+        /**
+         * Initialize action buttons
+         */
+        initActionButtons: function() {
+            var self = this;
+            
+            // View button
+            $('.qdr-tss-view-button').off('click').on('click', function() {
+                var id = $(this).data('id');
+                self.viewItem(id);
+            });
+            
+            // Edit button
+            $('.qdr-tss-edit-button').off('click').on('click', function() {
+                var id = $(this).data('id');
+                self.editItem(id);
+            });
+            
+            // Delete button
+            $('.qdr-tss-delete-button').off('click').on('click', function() {
+                var id = $(this).data('id');
+                self.showDeleteConfirmation(id);
+            });
+            
+            // Pagination
+            $('.qdr-tss-page-button').off('click').on('click', function() {
+                self.currentPage = parseInt($(this).data('page'));
+                self.loadItems();
+            });
+        },
+
+        /**
+         * Load items
+         */
+        loadItems: function() {
+            var self = this;
+            var data = {
+                action: 'qdr_tss_get_items',
+                nonce: qdr_tss.nonce,
+                page: this.currentPage,
+                per_page: this.perPage,
+                orderby: this.currentOrderBy,
+                order: this.currentOrder,
+                original_file_name: this.currentSearchFilename,
+                reference: this.currentSearchReference
+            };
+            
+            // Show loading indicator
+            $('#qdr-tss-items-list').html('<tr><td colspan="11" class="qdr-tss-loading"><span class="spinner is-active"></span> ' + qdr_tss.strings.loading + '</td></tr>');
+            
+            $.post(ajaxurl, data, function(response) {
+                if (response.success) {
+                    self.renderItems(response.data.items);
+                    self.renderPagination(response.data.total, response.data.total_pages);
+                } else {
+                    alert(response.data.message || qdr_tss.strings.error_loading_items);
+                }
+            }).fail(function() {
+                $('#qdr-tss-items-list').html('<tr><td colspan="11">' + qdr_tss.strings.error_loading_items_try_again + '</td></tr>');
+            });
+        },
+
+        /**
+         * Render items
+         */
+        renderItems: function(items) {
+            var self = this;
+            var $tbody = $('#qdr-tss-items-list');
+            $tbody.empty();
+            
+            if (!items || items.length === 0) {
+                $tbody.append('<tr><td colspan="11">' + qdr_tss.strings.no_items_found + '</td></tr>');
+                return;
+            }
+            
+            $.each(items, function(index, item) {
+                var statusClass = self.getItemStatusClass(item);
+                
+                var row = '<tr data-id="' + item.id + '" class="' + statusClass + '">';
+                
+                row += '<td>' + item.media_id + '</td>';
+                row += '<td>' + self.escapeHtml(item.original_file_name) + '</td>';
+                row += '<td>' + self.escapeHtml(item.description || '') + '</td>';
+                row += '<td>' + self.escapeHtml(item.message || '') + '</td>';
+                row += '<td>' + self.formatDate(item.active_from) + '</td>';
+                row += '<td>' + self.formatDate(item.active_to) + '</td>';
+                row += '<td>' + self.escapeHtml(item.reference || '') + '</td>';
+                row += '<td>' + self.formatDate(item.upload_date) + '</td>';
+                row += '<td>' + item.download_count + '</td>';
+                row += '<td>' + (item.last_download ? self.formatDate(item.last_download) : '-') + '</td>';
+                
+                row += '<td class="column-actions">';
+                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-delete-button" data-id="' + item.id + '" title="' + qdr_tss.strings.delete + '"><span class="dashicons dashicons-trash"></span></button>';
+                row += '</div>';
+                row += '</td>';
+                
+                row += '</tr>';
+                
+                $tbody.append(row);
+            });
+            
+            // Initialize action buttons
+            this.initActionButtons();
+            
+            // Update sorting indicators
+            this.updateSortIndicators();
+        },
+
+        /**
+         * Get item status class
+         */
+        getItemStatusClass: function(item) {
+            var now = new Date();
+            var activeFrom = new Date(item.active_from);
+            var activeTo = new Date(item.active_to);
+            
+            if (now < activeFrom) {
+                return 'qdr-tss-status-pending';
+            } else if (now > activeTo) {
+                return 'qdr-tss-status-expired';
+            } else {
+                return 'qdr-tss-status-active';
+            }
+        },
+
+        /**
+         * Render pagination
+         */
+        renderPagination: function(total, totalPages) {
+            var $info = $('.qdr-tss-pagination-info');
+            var $controls = $('.qdr-tss-pagination-controls');
+            
+            // Update info
+            var startItem = (this.currentPage - 1) * this.perPage + 1;
+            var endItem = Math.min(this.currentPage * this.perPage, total);
+            
+            if (total === 0) {
+                $info.html(qdr_tss.strings.no_items);
+                $controls.empty();
+                return;
+            }
+            
+            $info.html(qdr_tss.strings.showing + ' ' + startItem + ' - ' + endItem + ' ' + qdr_tss.strings.of + ' ' + total);
+            
+            // Update controls
+            $controls.empty();
+            
+            if (totalPages <= 1) {
+                return;
+            }
+            
+            // First page
+            if (this.currentPage > 1) {
+                $controls.append('<button class="button qdr-tss-page-button" data-page="1" title="' + qdr_tss.strings.first_page + '">&laquo;</button>');
+            }
+            
+            // Previous page
+            if (this.currentPage > 1) {
+                $controls.append('<button class="button qdr-tss-page-button" data-page="' + (this.currentPage - 1) + '" title="' + qdr_tss.strings.previous_page + '">&lsaquo;</button>');
+            }
+            
+            // Page numbers
+            var startPage = Math.max(1, this.currentPage - 2);
+            var endPage = Math.min(totalPages, startPage + 4);
+            
+            if (endPage - startPage < 4) {
+                startPage = Math.max(1, endPage - 4);
+            }
+            
+            for (var i = startPage; i <= endPage; i++) {
+                var activeClass = i === this.currentPage ? 'button-primary' : '';
+                $controls.append('<button class="button qdr-tss-page-button ' + activeClass + '" data-page="' + i + '">' + i + '</button>');
+            }
+            
+            // Next page
+            if (this.currentPage < totalPages) {
+                $controls.append('<button class="button qdr-tss-page-button" data-page="' + (this.currentPage + 1) + '" title="' + qdr_tss.strings.next_page + '">&rsaquo;</button>');
+            }
+            
+            // Last page
+            if (this.currentPage < totalPages) {
+                $controls.append('<button class="button qdr-tss-page-button" data-page="' + totalPages + '" title="' + qdr_tss.strings.last_page + '">&raquo;</button>');
+            }
+            
+            // Rebind click events
+            this.initActionButtons();
+        },
+
+        /**
+         * Update sort indicators
+         */
+        updateSortIndicators: function() {
+            // Remove all indicators
+            $('.qdr-tss-table th').removeClass('sorted asc desc');
+            
+            // Add indicator to current sort column
+            var $th = $('.qdr-tss-table th[data-sort="' + this.currentOrderBy + '"]');
+            $th.addClass('sorted ' + this.currentOrder.toLowerCase());
+        },
+
+        /**
+         * View item details
+         */
+        viewItem: function(id) {
+            var self = this;
+            
+            // Show loading modal
+            $('#qdr-tss-details-modal').show();
+            $('.qdr-tss-details-content').html('<div class="qdr-tss-loading"><span class="spinner is-active"></span> ' + qdr_tss.strings.loading + '</div>');
+            
+            // Get additional data via AJAX
+            var data = {
+                action: 'qdr_tss_get_item_details',
+                nonce: qdr_tss.nonce,
+                id: id
+            };
+            
+            $.post(ajaxurl, data, function(response) {
+                if (response.success) {
+                    var item = response.data.item;
+                    
+                    // Populate details
+                    $('#qdr-tss-details-media-id').text(item.media_id);
+                    $('#qdr-tss-details-filename').text(item.original_file_name);
+                    $('#qdr-tss-details-description').text(item.description || '-');
+                    $('#qdr-tss-details-message').text(item.message || '-');
+                    $('#qdr-tss-details-reference').text(item.reference || '-');
+                    $('#qdr-tss-details-active-period').text(self.formatDate(item.active_from) + ' - ' + self.formatDate(item.active_to));
+                    $('#qdr-tss-details-filesize').text(self.formatFileSize(item.file_size));
+                    $('#qdr-tss-details-download-count').text(item.download_count);
+                    $('#qdr-tss-details-last-download').text(item.last_download ? self.formatDate(item.last_download) : '-');
+                    
+                    // Permalink and shortcode
+                    $('#qdr-tss-details-permalink').html('<a href="' + item.permalink + '" target="_blank">' + item.permalink + '</a>');
+                    $('#qdr-tss-details-shortcode').html('<code>' + item.shortcode + '</code>');
+                    
+                    // Initialize copy buttons
+                    $('.qdr-tss-copy-shortcode').data('text', item.shortcode);
+                    $('.qdr-tss-copy-permalink').data('text', item.permalink);
+                } else {
+                    alert(response.data.message || qdr_tss.strings.error_loading_item_details);
+                    $('#qdr-tss-details-modal').hide();
+                }
+            }).fail(function() {
+                alert(qdr_tss.strings.error_loading_item_details_try_again);
+                $('#qdr-tss-details-modal').hide();
+            });
+        },
+
+        /**
+         * Edit item
+         */
+        editItem: function(id) {
+            // Find item in the table
+            var $row = $('tr[data-id="' + id + '"]');
+            if ($row.length === 0) return;
+            
+            var $cells = $row.find('td');
+            
+            // Populate form fields
+            $('#qdr-tss-edit-id').val(id);
+            $('#qdr-tss-edit-description').val($cells.eq(2).text());
+            $('#qdr-tss-edit-message').val($cells.eq(3).text());
+            
+            // Parse dates for datepicker
+            var activeFrom = $cells.eq(4).text();
+            var activeTo = $cells.eq(5).text();
+            
+            $('#qdr-tss-edit-active-from').val(activeFrom !== '-' ? activeFrom : '');
+            $('#qdr-tss-edit-active-to').val(activeTo !== '-' ? activeTo : '');
+            $('#qdr-tss-edit-reference').val($cells.eq(6).text());
+            
+            // Clear status
+            $('#qdr-tss-edit-status').html('');
+            
+            // Show modal
+            $('#qdr-tss-edit-modal').show();
+        },
+
+        /**
+         * Update item
+         */
+        updateItem: function() {
+            var self = this;
+            
+            // Show loading status
+            $('#qdr-tss-edit-status').html('<div class="qdr-tss-loading"><span class="spinner is-active"></span> ' + qdr_tss.strings.saving + '</div>');
+            
+            var data = {
+                action: 'qdr_tss_edit_item',
+                nonce: qdr_tss.nonce,
+                id: $('#qdr-tss-edit-id').val(),
+                description: $('#qdr-tss-edit-description').val(),
+                message: $('#qdr-tss-edit-message').val(),
+                active_from: $('#qdr-tss-edit-active-from').val(),
+                active_to: $('#qdr-tss-edit-active-to').val(),
+                reference: $('#qdr-tss-edit-reference').val()
+            };
+            
+            $.post(ajaxurl, data, function(response) {
+                if (response.success) {
+                    // Show success status
+                    $('#qdr-tss-edit-status').html('<div class="notice notice-success inline"><p>' + (response.data.message || qdr_tss.strings.changes_saved) + '</p></div>');
+                    
+                    // Hide modal after a delay
+                    setTimeout(function() {
+                        $('#qdr-tss-edit-modal').hide();
+                        
+                        // Reload items
+                        self.loadItems();
+                    }, 1000);
+                } else {
+                    // Show error status
+                    $('#qdr-tss-edit-status').html('<div class="notice notice-error inline"><p>' + (response.data.message || qdr_tss.strings.error_saving_changes) + '</p></div>');
+                }
+            }).fail(function() {
+                // Show error status
+                $('#qdr-tss-edit-status').html('<div class="notice notice-error inline"><p>' + qdr_tss.strings.error_saving_changes_try_again + '</p></div>');
+            });
+        },
+
+        /**
+         * Show delete confirmation
+         */
+        showDeleteConfirmation: function(id) {
+            $('#qdr-tss-delete-id').val(id);
+            $('#qdr-tss-confirm-delete-modal').show();
+        },
+
+        /**
+         * Delete item
+         */
+        deleteItem: function(id) {
+            var self = this;
+            var data = {
+                action: 'qdr_tss_delete_item',
+                nonce: qdr_tss.nonce,
+                id: id
+            };
+            
+            $.post(ajaxurl, data, function(response) {
+                if (response.success) {
+                    // Hide modal
+                    $('#qdr-tss-confirm-delete-modal').hide();
+                    
+                    // Show success message
+                    alert(response.data.message || qdr_tss.strings.file_deleted);
+                    
+                    // Reload items
+                    self.loadItems();
+                } else {
+                    alert(response.data.message || qdr_tss.strings.error_deleting_file);
+                }
+            }).fail(function() {
+                alert(qdr_tss.strings.error_deleting_file_try_again);
+            });
+        },
+
+        /**
+         * Format date
+         */
+        formatDate: function(dateString) {
+            if (!dateString || dateString === '0000-00-00 00:00:00') {
+                return '-';
+            }
+            
+            var date = new Date(dateString);
+            
+            // Check if date is valid
+            if (isNaN(date.getTime())) {
+                return dateString;
+            }
+            
+            return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
+        },
+
+        /**
+         * Format file size
+         */
+        formatFileSize: function(bytes) {
+            if (bytes === 0) return '0 Bytes';
+            
+            var k = 1024;
+            var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+            var i = Math.floor(Math.log(bytes) / Math.log(k));
+            
+            return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+        },
+
+        /**
+         * Escape HTML
+         */
+        escapeHtml: function(text) {
+            if (!text) return '';
+            
+            var map = {
+                '&': '&amp;',
+                '<': '&lt;',
+                '>': '&gt;',
+                '"': '&quot;',
+                "'": '&#039;'
+            };
+            
+            return text.toString().replace(/[&<>"']/g, function(m) { return map[m]; });
+        },
+
+        /**
+         * Copy to clipboard
+         */
+        copyToClipboard: function(text) {
+            var $temp = $('<input>');
+            $('body').append($temp);
+            $temp.val(text).select();
+            document.execCommand('copy');
+            $temp.remove();
+        }
+    };
+
+    // Settings page functionality
+    var QDR_TSS_Settings = {
+        /**
+         * Initialize the settings page
+         */
+        init: function() {
+            // Initialize event handlers
+            this.initEventHandlers();
+        },
+
+        /**
+         * Initialize event handlers
+         */
+        initEventHandlers: function() {
+            var self = this;
+
+            // Generate new API key
+            $('#qdr-tss-generate-key').on('click', function(e) {
+                e.preventDefault();
+                self.generateApiKey();
+            });
+            
+            // Save settings
+            $('#qdr-tss-settings-form').on('submit', function(e) {
+                e.preventDefault();
+                self.saveSettings();
+            });
+            
+            // Purge expired files
+            $('#qdr-tss-purge-expired').on('click', function(e) {
+                e.preventDefault();
+                self.purgeExpiredFiles();
+            });
+        },
+
+        /**
+         * Generate API key
+         */
+        generateApiKey: function() {
+            // Generate a random string for API key
+            var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
+            var apiKey = '';
+            for (var i = 0; i < 32; i++) {
+                apiKey += chars.charAt(Math.floor(Math.random() * chars.length));
+            }
+            
+            $('#qdr-tss-api-key').val(apiKey);
+        },
+
+        /**
+         * Save settings
+         */
+        saveSettings: function() {
+            var data = {
+                action: 'qdr_tss_save_settings',
+                nonce: qdr_tss.nonce,
+                api_key: $('#qdr-tss-api-key').val(),
+                media_category: $('#qdr-tss-media-category').val(),
+                max_upload_size: $('#qdr-tss-max-upload-size').val()
+            };
+            
+            $.post(ajaxurl, data, function(response) {
+                if (response.success) {
+                    alert(response.data.message);
+                } else {
+                    alert(response.data.message);
+                }
+            });
+        },
+
+        /**
+         * Purge expired files
+         */
+        purgeExpiredFiles: function() {
+            if (!confirm(qdr_tss.strings.confirm_purge)) {
+                return;
+            }
+            
+            var data = {
+                action: 'qdr_tss_purge_expired',
+                nonce: qdr_tss.nonce
+            };
+            
+            $.post(ajaxurl, data, function(response) {
+                if (response.success) {
+                    alert(response.data.message);
+                    // Reload page to update storage usage
+                    location.reload();
+                } else {
+                    alert(response.data.message);
+                }
+            });
+        }
+    };
+
+    // Document ready
+    $(function() {
+        // Initialize appropriate page functionality
+        if ($('.qdr-tss-table').length) {
+            QDR_TSS_Media.init();
+        }
+        
+        if ($('#qdr-tss-settings-form').length) {
+            QDR_TSS_Settings.init();
+        }
+    });
+
+})(jQuery);

+ 0 - 704
qdr-temporary-shared-storage/admin/media-page.php

@@ -1,704 +0,0 @@
-<?php
-/**
- * Admin Media Page for QDR Temporary Shared Storage
- *
- * @package QDR_Temporary_Shared_Storage
- */
-
-// If this file is called directly, abort.
-if (!defined('WPINC')) {
-    die;
-}
-
-/**
- * Render the Media | Shared Storage admin page
- */
-function qdr_tss_render_media_page() {
-    // Get current settings
-    $settings = get_option('qdr_tss_settings', array(
-        'api_key' => '',
-        'media_category' => 'shared-storage',
-        'max_upload_size' => 1073741824, // 1GB default
-    ));
-    
-    // Get total storage usage
-    global $wpdb;
-    $table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE;
-    $total_size = $wpdb->get_var("SELECT SUM(file_size) FROM $table_name");
-    $total_size = $total_size ? intval($total_size) : 0;
-    
-    $max_size = isset($settings['max_upload_size']) ? intval($settings['max_upload_size']) : 1073741824;
-    $usage_percent = ($max_size > 0) ? min(100, ($total_size / $max_size) * 100) : 0;
-    ?>
-    <div class="wrap qdr-tss-admin">
-        <h1><?php esc_html_e('Shared Storage', 'qdr-temporary-shared-storage'); ?></h1>
-        
-        <div class="qdr-tss-storage-summary">
-            <h2><?php esc_html_e('Storage Usage', 'qdr-temporary-shared-storage'); ?></h2>
-            <div class="qdr-tss-storage-bar">
-                <div class="qdr-tss-storage-used" style="width: <?php echo esc_attr($usage_percent); ?>%"></div>
-            </div>
-            <p>
-                <?php echo esc_html(qdr_tss_format_bytes($total_size)); ?> / <?php echo esc_html(qdr_tss_format_bytes($max_size)); ?>
-                (<?php echo number_format($usage_percent, 1); ?>%)
-            </p>
-        </div>
-        
-        <div class="qdr-tss-toolbar">
-            <div class="qdr-tss-search">
-                <input type="text" id="qdr-tss-search-filename" placeholder="<?php esc_attr_e('Search by filename', 'qdr-temporary-shared-storage'); ?>">
-                <input type="text" id="qdr-tss-search-reference" placeholder="<?php esc_attr_e('Search by reference', 'qdr-temporary-shared-storage'); ?>">
-                <button class="button" id="qdr-tss-search-button">
-                    <span class="dashicons dashicons-search"></span> <?php esc_html_e('Search', 'qdr-temporary-shared-storage'); ?>
-                </button>
-                <button class="button" id="qdr-tss-reset-search">
-                    <span class="dashicons dashicons-dismiss"></span> <?php esc_html_e('Reset', 'qdr-temporary-shared-storage'); ?>
-                </button>
-            </div>
-            
-            <div class="qdr-tss-actions">
-                <button class="button button-primary" id="qdr-tss-refresh">
-                    <span class="dashicons dashicons-update"></span> <?php esc_html_e('Refresh', 'qdr-temporary-shared-storage'); ?>
-                </button>
-            </div>
-        </div>
-        
-        <div class="qdr-tss-table-container">
-            <table class="wp-list-table widefat fixed striped qdr-tss-table">
-                <thead>
-                    <tr>
-                        <th class="column-media-id sortable" data-sort="media_id"><?php esc_html_e('Media ID', 'qdr-temporary-shared-storage'); ?> <span class="sorting-indicator"></span></th>
-                        <th class="column-filename sortable" data-sort="original_file_name"><?php esc_html_e('File Name', 'qdr-temporary-shared-storage'); ?> <span class="sorting-indicator"></span></th>
-                        <th class="column-description"><?php esc_html_e('Description', 'qdr-temporary-shared-storage'); ?></th>
-                        <th class="column-message"><?php esc_html_e('Message', 'qdr-temporary-shared-storage'); ?></th>
-                        <th class="column-active-from sortable" data-sort="active_from"><?php esc_html_e('Active From', 'qdr-temporary-shared-storage'); ?> <span class="sorting-indicator"></span></th>
-                        <th class="column-active-to sortable" data-sort="active_to"><?php esc_html_e('Active To', 'qdr-temporary-shared-storage'); ?> <span class="sorting-indicator"></span></th>
-                        <th class="column-reference sortable" data-sort="reference"><?php esc_html_e('Reference', 'qdr-temporary-shared-storage'); ?> <span class="sorting-indicator"></span></th>
-                        <th class="column-upload-date sortable" data-sort="upload_date"><?php esc_html_e('Upload Date', 'qdr-temporary-shared-storage'); ?> <span class="sorting-indicator"></span></th>
-                        <th class="column-download-count sortable" data-sort="download_count"><?php esc_html_e('Downloads', 'qdr-temporary-shared-storage'); ?> <span class="sorting-indicator"></span></th>
-                        <th class="column-last-download sortable" data-sort="last_download"><?php esc_html_e('Last Download', 'qdr-temporary-shared-storage'); ?> <span class="sorting-indicator"></span></th>
-                        <th class="column-actions"><?php esc_html_e('Actions', 'qdr-temporary-shared-storage'); ?></th>
-                    </tr>
-                </thead>
-                <tbody id="qdr-tss-items-list">
-                    <tr>
-                        <td colspan="11" class="qdr-tss-loading">
-                            <span class="spinner is-active"></span> <?php esc_html_e('Loading...', 'qdr-temporary-shared-storage'); ?>
-                        </td>
-                    </tr>
-                </tbody>
-            </table>
-        </div>
-        
-        <div class="qdr-tss-pagination">
-            <div class="qdr-tss-pagination-info"></div>
-            <div class="qdr-tss-pagination-controls"></div>
-        </div>
-    </div>
-    
-    <!-- Edit Item Modal -->
-    <div id="qdr-tss-edit-modal" class="qdr-tss-modal">
-        <div class="qdr-tss-modal-content">
-            <span class="qdr-tss-modal-close">&times;</span>
-            <h2><?php esc_html_e('Edit Shared File', 'qdr-temporary-shared-storage'); ?></h2>
-            
-            <form id="qdr-tss-edit-form">
-                <input type="hidden" id="qdr-tss-edit-id" name="id">
-                
-                <div class="qdr-tss-form-row">
-                    <label for="qdr-tss-edit-description"><?php esc_html_e('Description', 'qdr-temporary-shared-storage'); ?></label>
-                    <textarea id="qdr-tss-edit-description" name="description" rows="3"></textarea>
-                </div>
-                
-                <div class="qdr-tss-form-row">
-                    <label for="qdr-tss-edit-message"><?php esc_html_e('Message', 'qdr-temporary-shared-storage'); ?></label>
-                    <textarea id="qdr-tss-edit-message" name="message" rows="3"></textarea>
-                </div>
-                
-                <div class="qdr-tss-form-row">
-                    <label for="qdr-tss-edit-active-from"><?php esc_html_e('Active From', 'qdr-temporary-shared-storage'); ?></label>
-                    <input type="text" id="qdr-tss-edit-active-from" name="active_from" class="qdr-tss-datepicker" autocomplete="off">
-                </div>
-                
-                <div class="qdr-tss-form-row">
-                    <label for="qdr-tss-edit-active-to"><?php esc_html_e('Active To', 'qdr-temporary-shared-storage'); ?></label>
-                    <input type="text" id="qdr-tss-edit-active-to" name="active_to" class="qdr-tss-datepicker" autocomplete="off">
-                </div>
-                
-                <div class="qdr-tss-form-row">
-                    <label for="qdr-tss-edit-reference"><?php esc_html_e('Reference', 'qdr-temporary-shared-storage'); ?></label>
-                    <input type="text" id="qdr-tss-edit-reference" name="reference">
-                </div>
-                
-                <div class="qdr-tss-form-actions">
-                    <button type="submit" class="button button-primary"><?php esc_html_e('Save Changes', 'qdr-temporary-shared-storage'); ?></button>
-                    <button type="button" class="button qdr-tss-modal-cancel"><?php esc_html_e('Cancel', 'qdr-temporary-shared-storage'); ?></button>
-                </div>
-            </form>
-            
-            <div id="qdr-tss-edit-status"></div>
-        </div>
-    </div>
-    
-    <!-- View Details Modal -->
-    <div id="qdr-tss-details-modal" class="qdr-tss-modal">
-        <div class="qdr-tss-modal-content">
-            <span class="qdr-tss-modal-close">&times;</span>
-            <h2><?php esc_html_e('File Details', 'qdr-temporary-shared-storage'); ?></h2>
-            
-            <div class="qdr-tss-details-content">
-                <table class="widefat striped">
-                    <tr>
-                        <th><?php esc_html_e('Media ID', 'qdr-temporary-shared-storage'); ?></th>
-                        <td id="qdr-tss-details-media-id"></td>
-                    </tr>
-                    <tr>
-                        <th><?php esc_html_e('File Name', 'qdr-temporary-shared-storage'); ?></th>
-                        <td id="qdr-tss-details-filename"></td>
-                    </tr>
-                    <tr>
-                        <th><?php esc_html_e('Description', 'qdr-temporary-shared-storage'); ?></th>
-                        <td id="qdr-tss-details-description"></td>
-                    </tr>
-                    <tr>
-                        <th><?php esc_html_e('Message', 'qdr-temporary-shared-storage'); ?></th>
-                        <td id="qdr-tss-details-message"></td>
-                    </tr>
-                    <tr>
-                        <th><?php esc_html_e('Reference', 'qdr-temporary-shared-storage'); ?></th>
-                        <td id="qdr-tss-details-reference"></td>
-                    </tr>
-                    <tr>
-                        <th><?php esc_html_e('Active Period', 'qdr-temporary-shared-storage'); ?></th>
-                        <td id="qdr-tss-details-active-period"></td>
-                    </tr>
-                    <tr>
-                        <th><?php esc_html_e('File Size', 'qdr-temporary-shared-storage'); ?></th>
-                        <td id="qdr-tss-details-filesize"></td>
-                    </tr>
-                    <tr>
-                        <th><?php esc_html_e('Download Count', 'qdr-temporary-shared-storage'); ?></th>
-                        <td id="qdr-tss-details-download-count"></td>
-                    </tr>
-                    <tr>
-                        <th><?php esc_html_e('Last Download', 'qdr-temporary-shared-storage'); ?></th>
-                        <td id="qdr-tss-details-last-download"></td>
-                    </tr>
-                    <tr>
-                        <th><?php esc_html_e('Permalink', 'qdr-temporary-shared-storage'); ?></th>
-                        <td id="qdr-tss-details-permalink"></td>
-                    </tr>
-                    <tr>
-                        <th><?php esc_html_e('Shortcode', 'qdr-temporary-shared-storage'); ?></th>
-                        <td id="qdr-tss-details-shortcode"></td>
-                    </tr>
-                </table>
-            </div>
-            
-            <div class="qdr-tss-form-actions">
-                <button type="button" class="button button-secondary qdr-tss-copy-shortcode"><?php esc_html_e('Copy Shortcode', 'qdr-temporary-shared-storage'); ?></button>
-                <button type="button" class="button button-secondary qdr-tss-copy-permalink"><?php esc_html_e('Copy Permalink', 'qdr-temporary-shared-storage'); ?></button>
-                <button type="button" class="button qdr-tss-modal-cancel"><?php esc_html_e('Close', 'qdr-temporary-shared-storage'); ?></button>
-            </div>
-        </div>
-    </div>
-    
-    <!-- Confirm Delete Modal -->
-    <div id="qdr-tss-confirm-delete-modal" class="qdr-tss-modal">
-        <div class="qdr-tss-modal-content qdr-tss-modal-sm">
-            <span class="qdr-tss-modal-close">&times;</span>
-            <h2><?php esc_html_e('Confirm Delete', 'qdr-temporary-shared-storage'); ?></h2>
-            
-            <p><?php esc_html_e('Are you sure you want to delete this file? This action cannot be undone.', 'qdr-temporary-shared-storage'); ?></p>
-            
-            <div class="qdr-tss-form-actions">
-                <button type="button" class="button button-primary qdr-tss-confirm-delete-button"><?php esc_html_e('Delete', 'qdr-temporary-shared-storage'); ?></button>
-                <button type="button" class="button qdr-tss-modal-cancel"><?php esc_html_e('Cancel', 'qdr-temporary-shared-storage'); ?></button>
-            </div>
-            
-            <input type="hidden" id="qdr-tss-delete-id">
-        </div>
-    </div>
-    
-    <script type="text/javascript">
-        jQuery(document).ready(function($) {
-            var currentPage = 1;
-            var perPage = 10;
-            var totalPages = 1;
-            var currentOrderBy = 'upload_date';
-            var currentOrder = 'DESC';
-            var currentSearchFilename = '';
-            var currentSearchReference = '';
-            
-            // Load items
-            function loadItems() {
-                var data = {
-                    action: 'qdr_tss_get_items',
-                    nonce: qdr_tss.nonce,
-                    page: currentPage,
-                    per_page: perPage,
-                    orderby: currentOrderBy,
-                    order: currentOrder,
-                    original_file_name: currentSearchFilename,
-                    reference: currentSearchReference
-                };
-                
-                // Show loading indicator
-                $('#qdr-tss-items-list').html('<tr><td colspan="11" class="qdr-tss-loading"><span class="spinner is-active"></span> <?php esc_html_e('Loading...', 'qdr-temporary-shared-storage'); ?></td></tr>');
-                
-                $.post(ajaxurl, data, function(response) {
-                    if (response.success) {
-                        renderItems(response.data.items);
-                        renderPagination(response.data.total, response.data.total_pages);
-                    } else {
-                        alert(response.data.message || '<?php esc_html_e('Error loading items.', 'qdr-temporary-shared-storage'); ?>');
-                    }
-                }).fail(function() {
-                    $('#qdr-tss-items-list').html('<tr><td colspan="11"><?php esc_html_e('Error loading items. Please try again.', 'qdr-temporary-shared-storage'); ?></td></tr>');
-                });
-            }
-            
-            // Render items
-            function renderItems(items) {
-                var $tbody = $('#qdr-tss-items-list');
-                $tbody.empty();
-                
-                if (!items || items.length === 0) {
-                    $tbody.append('<tr><td colspan="11"><?php esc_html_e('No items found.', 'qdr-temporary-shared-storage'); ?></td></tr>');
-                    return;
-                }
-                
-                $.each(items, function(index, item) {
-                    var statusClass = '';
-                    var now = new Date();
-                    var activeFrom = new Date(item.active_from);
-                    var activeTo = new Date(item.active_to);
-                    
-                    if (now < activeFrom) {
-                        statusClass = 'qdr-tss-status-pending';
-                    } else if (now > activeTo) {
-                        statusClass = 'qdr-tss-status-expired';
-                    } else {
-                        statusClass = 'qdr-tss-status-active';
-                    }
-                    
-                    var row = '<tr data-id="' + item.id + '" class="' + statusClass + '">';
-                    
-                    row += '<td>' + item.media_id + '</td>';
-                    row += '<td>' + escapeHtml(item.original_file_name) + '</td>';
-                    row += '<td>' + escapeHtml(item.description || '') + '</td>';
-                    row += '<td>' + escapeHtml(item.message || '') + '</td>';
-                    row += '<td>' + formatDate(item.active_from) + '</td>';
-                    row += '<td>' + formatDate(item.active_to) + '</td>';
-                    row += '<td>' + escapeHtml(item.reference || '') + '</td>';
-                    row += '<td>' + formatDate(item.upload_date) + '</td>';
-                    row += '<td>' + item.download_count + '</td>';
-                    row += '<td>' + (item.last_download ? formatDate(item.last_download) : '-') + '</td>';
-                    
-                    row += '<td class="column-actions">';
-                    row += '<div class="qdr-tss-action-buttons">';
-                    row += '<button class="button qdr-tss-view-button" data-id="' + item.id + '" title="<?php esc_attr_e('View Details', 'qdr-temporary-shared-storage'); ?>"><span class="dashicons dashicons-visibility"></span></button>';
-                    row += '<button class="button qdr-tss-edit-button" data-id="' + item.id + '" title="<?php esc_attr_e('Edit', 'qdr-temporary-shared-storage'); ?>"><span class="dashicons dashicons-edit"></span></button>';
-                    row += '<button class="button qdr-tss-delete-button" data-id="' + item.id + '" title="<?php esc_attr_e('Delete', 'qdr-temporary-shared-storage'); ?>"><span class="dashicons dashicons-trash"></span></button>';
-                    row += '</div>';
-                    row += '</td>';
-                    
-                    row += '</tr>';
-                    
-                    $tbody.append(row);
-                });
-                
-                // Initialize action buttons
-                initActionButtons();
-                
-                // Update sorting indicators
-                updateSortIndicators();
-            }
-            
-            // Initialize action buttons
-            function initActionButtons() {
-                // View button
-                $('.qdr-tss-view-button').on('click', function() {
-                    var id = $(this).data('id');
-                    viewItem(id);
-                });
-                
-                // Edit button
-                $('.qdr-tss-edit-button').on('click', function() {
-                    var id = $(this).data('id');
-                    editItem(id);
-                });
-                
-                // Delete button
-                $('.qdr-tss-delete-button').on('click', function() {
-                    var id = $(this).data('id');
-                    showDeleteConfirmation(id);
-                });
-            }
-            
-            // Format date
-            function formatDate(dateString) {
-                if (!dateString || dateString === '0000-00-00 00:00:00') {
-                    return '-';
-                }
-                
-                var date = new Date(dateString);
-                
-                // Check if date is valid
-                if (isNaN(date.getTime())) {
-                    return dateString;
-                }
-                
-                return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
-            }
-            
-            // Format file size
-            function formatFileSize(bytes) {
-                if (bytes === 0) return '0 Bytes';
-                
-                var k = 1024;
-                var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
-                var i = Math.floor(Math.log(bytes) / Math.log(k));
-                
-                return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
-            }
-            
-            // Escape HTML
-            function escapeHtml(text) {
-                if (!text) return '';
-                
-                var map = {
-                    '&': '&amp;',
-                    '<': '&lt;',
-                    '>': '&gt;',
-                    '"': '&quot;',
-                    "'": '&#039;'
-                };
-                
-                return text.toString().replace(/[&<>"']/g, function(m) { return map[m]; });
-            }
-            
-            // Render pagination
-            function renderPagination(total, totalPages) {
-                var $info = $('.qdr-tss-pagination-info');
-                var $controls = $('.qdr-tss-pagination-controls');
-                
-                // Update info
-                var startItem = (currentPage - 1) * perPage + 1;
-                var endItem = Math.min(currentPage * perPage, total);
-                
-                if (total === 0) {
-                    $info.html('<?php esc_html_e('No items', 'qdr-temporary-shared-storage'); ?>');
-                    $controls.empty();
-                    return;
-                }
-                
-                $info.html('<?php esc_html_e('Showing', 'qdr-temporary-shared-storage'); ?> ' + startItem + ' - ' + endItem + ' <?php esc_html_e('of', 'qdr-temporary-shared-storage'); ?> ' + total);
-                
-                // Update controls
-                $controls.empty();
-                
-                if (totalPages <= 1) {
-                    return;
-                }
-                
-                // First page
-                if (currentPage > 1) {
-                    $controls.append('<button class="button qdr-tss-page-button" data-page="1" title="<?php esc_attr_e('First Page', 'qdr-temporary-shared-storage'); ?>">&laquo;</button>');
-                }
-                
-                // Previous page
-                if (currentPage > 1) {
-                    $controls.append('<button class="button qdr-tss-page-button" data-page="' + (currentPage - 1) + '" title="<?php esc_attr_e('Previous Page', 'qdr-temporary-shared-storage'); ?>">&lsaquo;</button>');
-                }
-                
-                // Page numbers
-                var startPage = Math.max(1, currentPage - 2);
-                var endPage = Math.min(totalPages, startPage + 4);
-                
-                if (endPage - startPage < 4) {
-                    startPage = Math.max(1, endPage - 4);
-                }
-                
-                for (var i = startPage; i <= endPage; i++) {
-                    var activeClass = i === currentPage ? 'button-primary' : '';
-                    $controls.append('<button class="button qdr-tss-page-button ' + activeClass + '" data-page="' + i + '">' + i + '</button>');
-                }
-                
-                // Next page
-                if (currentPage < totalPages) {
-                    $controls.append('<button class="button qdr-tss-page-button" data-page="' + (currentPage + 1) + '" title="<?php esc_attr_e('Next Page', 'qdr-temporary-shared-storage'); ?>">&rsaquo;</button>');
-                }
-                
-                // Last page
-                if (currentPage < totalPages) {
-                    $controls.append('<button class="button qdr-tss-page-button" data-page="' + totalPages + '" title="<?php esc_attr_e('Last Page', 'qdr-temporary-shared-storage'); ?>">&raquo;</button>');
-                }
-                
-                // Bind click event to page buttons
-                $('.qdr-tss-page-button').on('click', function() {
-                    currentPage = parseInt($(this).data('page'));
-                    loadItems();
-                });
-            }
-            
-            // Update sort indicators
-            function updateSortIndicators() {
-                // Remove all indicators
-                $('.qdr-tss-table th').removeClass('sorted asc desc');
-                
-                // Add indicator to current sort column
-                var $th = $('.qdr-tss-table th[data-sort="' + currentOrderBy + '"]');
-                $th.addClass('sorted ' + currentOrder.toLowerCase());
-            }
-            
-            // View item details
-            function viewItem(id) {
-                // Find item in the table
-                var $row = $('tr[data-id="' + id + '"]');
-                if ($row.length === 0) return;
-                
-                var $cells = $row.find('td');
-                
-                var mediaId = $cells.eq(0).text();
-                var fileName = $cells.eq(1).text();
-                var description = $cells.eq(2).text();
-                var message = $cells.eq(3).text();
-                var activeFrom = $cells.eq(4).text();
-                var activeTo = $cells.eq(5).text();
-                var reference = $cells.eq(6).text();
-                var uploadDate = $cells.eq(7).text();
-                var downloadCount = $cells.eq(8).text();
-                var lastDownload = $cells.eq(9).text();
-                
-                // Get additional data via AJAX
-                var data = {
-                    action: 'qdr_tss_get_item_details',
-                    nonce: qdr_tss.nonce,
-                    id: id,
-                    media_id: mediaId
-                };
-                
-                $.post(ajaxurl, data, function(response) {
-                    if (response.success) {
-                        var item = response.data.item;
-                        
-                        // Populate details
-                        $('#qdr-tss-details-media-id').text(mediaId);
-                        $('#qdr-tss-details-filename').text(fileName);
-                        $('#qdr-tss-details-description').text(description || '-');
-                        $('#qdr-tss-details-message').text(message || '-');
-                        $('#qdr-tss-details-reference').text(reference || '-');
-                        $('#qdr-tss-details-active-period').text(activeFrom + ' - ' + activeTo);
-                        $('#qdr-tss-details-filesize').text(formatFileSize(item.file_size));
-                        $('#qdr-tss-details-download-count').text(downloadCount);
-                        $('#qdr-tss-details-last-download').text(lastDownload);
-                        
-                        // Permalink and shortcode
-                        var permalink = '<?php echo esc_url(site_url('shared-file-download/')); ?>' + mediaId;
-                        var shortcode = '[qdr_tss_download id="' + mediaId + '"]';
-                        
-                        $('#qdr-tss-details-permalink').html('<a href="' + permalink + '" target="_blank">' + permalink + '</a>');
-                        $('#qdr-tss-details-shortcode').html('<code>' + shortcode + '</code>');
-                        
-                        // Show modal
-                        $('#qdr-tss-details-modal').show();
-                    } else {
-                        alert(response.data.message || '<?php esc_html_e('Error loading item details.', 'qdr-temporary-shared-storage'); ?>');
-                    }
-                }).fail(function() {
-                    alert('<?php esc_html_e('Error loading item details. Please try again.', 'qdr-temporary-shared-storage'); ?>');
-                });
-            }
-            
-            // Edit item
-            function editItem(id) {
-                // Find item in the table
-                var $row = $('tr[data-id="' + id + '"]');
-                if ($row.length === 0) return;
-                
-                var $cells = $row.find('td');
-                
-                // Populate form fields
-                $('#qdr-tss-edit-id').val(id);
-                $('#qdr-tss-edit-description').val($cells.eq(2).text());
-                $('#qdr-tss-edit-message').val($cells.eq(3).text());
-                
-                // Parse dates for datepicker
-                var activeFrom = $cells.eq(4).text();
-                var activeTo = $cells.eq(5).text();
-                
-                $('#qdr-tss-edit-active-from').val(activeFrom !== '-' ? activeFrom : '');
-                $('#qdr-tss-edit-active-to').val(activeTo !== '-' ? activeTo : '');
-                $('#qdr-tss-edit-reference').val($cells.eq(6).text());
-                
-                // Clear status
-                $('#qdr-tss-edit-status').html('');
-                
-                // Show modal
-                $('#qdr-tss-edit-modal').show();
-                
-                // Initialize datepickers
-                $('.qdr-tss-datepicker').datepicker({
-                    dateFormat: 'yy-mm-dd',
-                    changeMonth: true,
-                    changeYear: true
-                });
-            }
-            
-            // Show delete confirmation
-            function showDeleteConfirmation(id) {
-                $('#qdr-tss-delete-id').val(id);
-                $('#qdr-tss-confirm-delete-modal').show();
-            }
-            
-            // Delete item
-            function deleteItem(id) {
-                var data = {
-                    action: 'qdr_tss_delete_item',
-                    nonce: qdr_tss.nonce,
-                    id: id
-                };
-                
-                $.post(ajaxurl, data, function(response) {
-                    if (response.success) {
-                        // Hide modal
-                        $('#qdr-tss-confirm-delete-modal').hide();
-                        
-                        // Show success message
-                        alert(response.data.message || '<?php esc_html_e('File deleted successfully.', 'qdr-temporary-shared-storage'); ?>');
-                        
-                        // Reload items
-                        loadItems();
-                    } else {
-                        alert(response.data.message || '<?php esc_html_e('Error deleting file.', 'qdr-temporary-shared-storage'); ?>');
-                    }
-                }).fail(function() {
-                    alert('<?php esc_html_e('Error deleting file. Please try again.', 'qdr-temporary-shared-storage'); ?>');
-                });
-            }
-            
-            // Copy text to clipboard
-            function copyToClipboard(text) {
-                var $temp = $('<input>');
-                $('body').append($temp);
-                $temp.val(text).select();
-                document.execCommand('copy');
-                $temp.remove();
-            }
-            
-            // Handle form submission
-            $('#qdr-tss-edit-form').on('submit', function(e) {
-                e.preventDefault();
-                
-                // Show loading status
-                $('#qdr-tss-edit-status').html('<div class="qdr-tss-loading"><span class="spinner is-active"></span> <?php esc_html_e('Saving...', 'qdr-temporary-shared-storage'); ?></div>');
-                
-                var data = {
-                    action: 'qdr_tss_edit_item',
-                    nonce: qdr_tss.nonce,
-                    id: $('#qdr-tss-edit-id').val(),
-                    description: $('#qdr-tss-edit-description').val(),
-                    message: $('#qdr-tss-edit-message').val(),
-                    active_from: $('#qdr-tss-edit-active-from').val(),
-                    active_to: $('#qdr-tss-edit-active-to').val(),
-                    reference: $('#qdr-tss-edit-reference').val()
-                };
-                
-                $.post(ajaxurl, data, function(response) {
-                    if (response.success) {
-                        // Show success status
-                        $('#qdr-tss-edit-status').html('<div class="notice notice-success inline"><p>' + (response.data.message || '<?php esc_html_e('Changes saved successfully.', 'qdr-temporary-shared-storage'); ?>') + '</p></div>');
-                        
-                        // Hide modal after a delay
-                        setTimeout(function() {
-                            $('#qdr-tss-edit-modal').hide();
-                            
-                            // Reload items
-                            loadItems();
-                        }, 1000);
-                    } else {
-                        // Show error status
-                        $('#qdr-tss-edit-status').html('<div class="notice notice-error inline"><p>' + (response.data.message || '<?php esc_html_e('Error saving changes.', 'qdr-temporary-shared-storage'); ?>') + '</p></div>');
-                    }
-                }).fail(function() {
-                    // Show error status
-                    $('#qdr-tss-edit-status').html('<div class="notice notice-error inline"><p><?php esc_html_e('Error saving changes. Please try again.', 'qdr-temporary-shared-storage'); ?></p></div>');
-                });
-            });
-            
-            // Handle sorting
-            $('.qdr-tss-table th.sortable').on('click', function() {
-                var column = $(this).data('sort');
-                
-                if (currentOrderBy === column) {
-                    currentOrder = currentOrder === 'ASC' ? 'DESC' : 'ASC';
-                } else {
-                    currentOrderBy = column;
-                    currentOrder = 'ASC';
-                }
-                
-                loadItems();
-            });
-            
-            // Handle search
-            $('#qdr-tss-search-button').on('click', function() {
-                currentSearchFilename = $('#qdr-tss-search-filename').val();
-                currentSearchReference = $('#qdr-tss-search-reference').val();
-                currentPage = 1;
-                loadItems();
-            });
-            
-            // Handle search reset
-            $('#qdr-tss-reset-search').on('click', function() {
-                $('#qdr-tss-search-filename').val('');
-                $('#qdr-tss-search-reference').val('');
-                currentSearchFilename = '';
-                currentSearchReference = '';
-                currentPage = 1;
-                loadItems();
-            });
-            
-            // Handle refresh button
-            $('#qdr-tss-refresh').on('click', function() {
-                loadItems();
-            });
-            
-            // Handle modal close
-            $('.qdr-tss-modal-close, .qdr-tss-modal-cancel').on('click', function() {
-                $('.qdr-tss-modal').hide();
-            });
-            
-            // Close modal when clicking outside
-            $(window).on('click', function(e) {
-                if ($(e.target).hasClass('qdr-tss-modal')) {
-                    $('.qdr-tss-modal').hide();
-                }
-            });
-            
-            // Handle copy shortcode
-            $('.qdr-tss-copy-shortcode').on('click', function() {
-                var shortcode = $('#qdr-tss-details-shortcode code').text();
-                copyToClipboard(shortcode);
-                alert('<?php esc_html_e('Shortcode copied to clipboard.', 'qdr-temporary-shared-storage'); ?>');
-            });
-            
-            // Handle copy permalink
-            $('.qdr-tss-copy-permalink').on('click', function() {
-                var permalink = $('#qdr-tss-details-permalink a').attr('href');
-                copyToClipboard(permalink);
-                alert('<?php esc_html_e('Permalink copied to clipboard.', 'qdr-temporary-shared-storage'); ?>');
-            });
-            
-            // Handle confirm delete
-            $('.qdr-tss-confirm-delete-button').on('click', function() {
-                var id = $('#qdr-tss-delete-id').val();
-                deleteItem(id);
-            });
-            
-            // Initial load
-            loadItems();
-        });
-    </script>
-    <?php
-}

+ 210 - 0
qdr-temporary-shared-storage/admin/partials/media-page.php

@@ -0,0 +1,210 @@
+<?php
+/**
+ * Admin Media Page for QDR Temporary Shared Storage
+ *
+ * @package QDR_Temporary_Shared_Storage
+ */
+
+// If this file is called directly, abort.
+if (!defined('WPINC')) {
+    die;
+}
+
+// Get current settings
+$settings = QDR_TSS_Settings::instance()->get_settings();
+$db_manager = QDR_TSS_DB_Manager::instance();
+
+// Get total storage usage
+$total_size = $db_manager->get_total_storage();
+$max_size = $settings['max_upload_size'];
+$usage_percent = ($max_size > 0) ? min(100, ($total_size / $max_size) * 100) : 0;
+?>
+<div class="wrap qdr-tss-admin">
+    <h1><?php esc_html_e('Shared Storage', 'qdr-temporary-shared-storage'); ?></h1>
+    
+    <div class="qdr-tss-storage-summary">
+        <h2><?php esc_html_e('Storage Usage', 'qdr-temporary-shared-storage'); ?></h2>
+        <div class="qdr-tss-storage-bar">
+            <div class="qdr-tss-storage-used" style="width: <?php echo esc_attr($usage_percent); ?>%"></div>
+        </div>
+        <p>
+            <?php echo esc_html(qdr_tss_format_bytes($total_size)); ?> / <?php echo esc_html(qdr_tss_format_bytes($max_size)); ?>
+            (<?php echo number_format($usage_percent, 1); ?>%)
+        </p>
+    </div>
+    
+    <div class="qdr-tss-toolbar">
+        <div class="qdr-tss-search">
+            <input type="text" id="qdr-tss-search-filename" placeholder="<?php esc_attr_e('Search by filename', 'qdr-temporary-shared-storage'); ?>">
+            <input type="text" id="qdr-tss-search-reference" placeholder="<?php esc_attr_e('Search by reference', 'qdr-temporary-shared-storage'); ?>">
+            <button class="button" id="qdr-tss-search-button">
+                <span class="dashicons dashicons-search"></span> <?php esc_html_e('Search', 'qdr-temporary-shared-storage'); ?>
+            </button>
+            <button class="button" id="qdr-tss-reset-search">
+                <span class="dashicons dashicons-dismiss"></span> <?php esc_html_e('Reset', 'qdr-temporary-shared-storage'); ?>
+            </button>
+        </div>
+        
+        <div class="qdr-tss-actions">
+            <button class="button button-primary" id="qdr-tss-refresh">
+                <span class="dashicons dashicons-update"></span> <?php esc_html_e('Refresh', 'qdr-temporary-shared-storage'); ?>
+            </button>
+        </div>
+    </div>
+    
+    <div class="qdr-tss-table-container">
+        <table class="wp-list-table widefat fixed striped qdr-tss-table">
+            <thead>
+                <tr>
+                    <th class="column-media-id sortable" data-sort="media_id"><?php esc_html_e('Media ID', 'qdr-temporary-shared-storage'); ?> <span class="sorting-indicator"></span></th>
+                    <th class="column-filename sortable" data-sort="original_file_name"><?php esc_html_e('File Name', 'qdr-temporary-shared-storage'); ?> <span class="sorting-indicator"></span></th>
+                    <th class="column-description"><?php esc_html_e('Description', 'qdr-temporary-shared-storage'); ?></th>
+                    <th class="column-message"><?php esc_html_e('Message', 'qdr-temporary-shared-storage'); ?></th>
+                    <th class="column-active-from sortable" data-sort="active_from"><?php esc_html_e('Active From', 'qdr-temporary-shared-storage'); ?> <span class="sorting-indicator"></span></th>
+                    <th class="column-active-to sortable" data-sort="active_to"><?php esc_html_e('Active To', 'qdr-temporary-shared-storage'); ?> <span class="sorting-indicator"></span></th>
+                    <th class="column-reference sortable" data-sort="reference"><?php esc_html_e('Reference', 'qdr-temporary-shared-storage'); ?> <span class="sorting-indicator"></span></th>
+                    <th class="column-upload-date sortable" data-sort="upload_date"><?php esc_html_e('Upload Date', 'qdr-temporary-shared-storage'); ?> <span class="sorting-indicator"></span></th>
+                    <th class="column-download-count sortable" data-sort="download_count"><?php esc_html_e('Downloads', 'qdr-temporary-shared-storage'); ?> <span class="sorting-indicator"></span></th>
+                    <th class="column-last-download sortable" data-sort="last_download"><?php esc_html_e('Last Download', 'qdr-temporary-shared-storage'); ?> <span class="sorting-indicator"></span></th>
+                    <th class="column-actions"><?php esc_html_e('Actions', 'qdr-temporary-shared-storage'); ?></th>
+                </tr>
+            </thead>
+            <tbody id="qdr-tss-items-list">
+                <tr>
+                    <td colspan="11" class="qdr-tss-loading">
+                        <span class="spinner is-active"></span> <?php esc_html_e('Loading...', 'qdr-temporary-shared-storage'); ?>
+                    </td>
+                </tr>
+            </tbody>
+        </table>
+    </div>
+    
+    <div class="qdr-tss-pagination">
+        <div class="qdr-tss-pagination-info"></div>
+        <div class="qdr-tss-pagination-controls"></div>
+    </div>
+</div>
+
+<!-- Edit Item Modal -->
+<div id="qdr-tss-edit-modal" class="qdr-tss-modal">
+    <div class="qdr-tss-modal-content">
+        <span class="qdr-tss-modal-close">&times;</span>
+        <h2><?php esc_html_e('Edit Shared File', 'qdr-temporary-shared-storage'); ?></h2>
+        
+        <form id="qdr-tss-edit-form">
+            <input type="hidden" id="qdr-tss-edit-id" name="id">
+            
+            <div class="qdr-tss-form-row">
+                <label for="qdr-tss-edit-description"><?php esc_html_e('Description', 'qdr-temporary-shared-storage'); ?></label>
+                <textarea id="qdr-tss-edit-description" name="description" rows="3"></textarea>
+            </div>
+            
+            <div class="qdr-tss-form-row">
+                <label for="qdr-tss-edit-message"><?php esc_html_e('Message', 'qdr-temporary-shared-storage'); ?></label>
+                <textarea id="qdr-tss-edit-message" name="message" rows="3"></textarea>
+            </div>
+            
+            <div class="qdr-tss-form-row">
+                <label for="qdr-tss-edit-active-from"><?php esc_html_e('Active From', 'qdr-temporary-shared-storage'); ?></label>
+                <input type="text" id="qdr-tss-edit-active-from" name="active_from" class="qdr-tss-datepicker" autocomplete="off">
+            </div>
+            
+            <div class="qdr-tss-form-row">
+                <label for="qdr-tss-edit-active-to"><?php esc_html_e('Active To', 'qdr-temporary-shared-storage'); ?></label>
+                <input type="text" id="qdr-tss-edit-active-to" name="active_to" class="qdr-tss-datepicker" autocomplete="off">
+            </div>
+            
+            <div class="qdr-tss-form-row">
+                <label for="qdr-tss-edit-reference"><?php esc_html_e('Reference', 'qdr-temporary-shared-storage'); ?></label>
+                <input type="text" id="qdr-tss-edit-reference" name="reference">
+            </div>
+            
+            <div class="qdr-tss-form-actions">
+                <button type="submit" class="button button-primary"><?php esc_html_e('Save Changes', 'qdr-temporary-shared-storage'); ?></button>
+                <button type="button" class="button qdr-tss-modal-cancel"><?php esc_html_e('Cancel', 'qdr-temporary-shared-storage'); ?></button>
+            </div>
+        </form>
+        
+        <div id="qdr-tss-edit-status"></div>
+    </div>
+</div>
+
+<!-- View Details Modal -->
+<div id="qdr-tss-details-modal" class="qdr-tss-modal">
+    <div class="qdr-tss-modal-content">
+        <span class="qdr-tss-modal-close">&times;</span>
+        <h2><?php esc_html_e('File Details', 'qdr-temporary-shared-storage'); ?></h2>
+        
+        <div class="qdr-tss-details-content">
+            <table class="widefat striped">
+                <tr>
+                    <th><?php esc_html_e('Media ID', 'qdr-temporary-shared-storage'); ?></th>
+                    <td id="qdr-tss-details-media-id"></td>
+                </tr>
+                <tr>
+                    <th><?php esc_html_e('File Name', 'qdr-temporary-shared-storage'); ?></th>
+                    <td id="qdr-tss-details-filename"></td>
+                </tr>
+                <tr>
+                    <th><?php esc_html_e('Description', 'qdr-temporary-shared-storage'); ?></th>
+                    <td id="qdr-tss-details-description"></td>
+                </tr>
+                <tr>
+                    <th><?php esc_html_e('Message', 'qdr-temporary-shared-storage'); ?></th>
+                    <td id="qdr-tss-details-message"></td>
+                </tr>
+                <tr>
+                    <th><?php esc_html_e('Reference', 'qdr-temporary-shared-storage'); ?></th>
+                    <td id="qdr-tss-details-reference"></td>
+                </tr>
+                <tr>
+                    <th><?php esc_html_e('Active Period', 'qdr-temporary-shared-storage'); ?></th>
+                    <td id="qdr-tss-details-active-period"></td>
+                </tr>
+                <tr>
+                    <th><?php esc_html_e('File Size', 'qdr-temporary-shared-storage'); ?></th>
+                    <td id="qdr-tss-details-filesize"></td>
+                </tr>
+                <tr>
+                    <th><?php esc_html_e('Download Count', 'qdr-temporary-shared-storage'); ?></th>
+                    <td id="qdr-tss-details-download-count"></td>
+                </tr>
+                <tr>
+                    <th><?php esc_html_e('Last Download', 'qdr-temporary-shared-storage'); ?></th>
+                    <td id="qdr-tss-details-last-download"></td>
+                </tr>
+                <tr>
+                    <th><?php esc_html_e('Permalink', 'qdr-temporary-shared-storage'); ?></th>
+                    <td id="qdr-tss-details-permalink"></td>
+                </tr>
+                <tr>
+                    <th><?php esc_html_e('Shortcode', 'qdr-temporary-shared-storage'); ?></th>
+                    <td id="qdr-tss-details-shortcode"></td>
+                </tr>
+            </table>
+        </div>
+        
+        <div class="qdr-tss-form-actions">
+            <button type="button" class="button button-secondary qdr-tss-copy-shortcode"><?php esc_html_e('Copy Shortcode', 'qdr-temporary-shared-storage'); ?></button>
+            <button type="button" class="button button-secondary qdr-tss-copy-permalink"><?php esc_html_e('Copy Permalink', 'qdr-temporary-shared-storage'); ?></button>
+            <button type="button" class="button qdr-tss-modal-cancel"><?php esc_html_e('Close', 'qdr-temporary-shared-storage'); ?></button>
+        </div>
+    </div>
+</div>
+
+<!-- Confirm Delete Modal -->
+<div id="qdr-tss-confirm-delete-modal" class="qdr-tss-modal">
+    <div class="qdr-tss-modal-content qdr-tss-modal-sm">
+        <span class="qdr-tss-modal-close">&times;</span>
+        <h2><?php esc_html_e('Confirm Delete', 'qdr-temporary-shared-storage'); ?></h2>
+        
+        <p><?php esc_html_e('Are you sure you want to delete this file? This action cannot be undone.', 'qdr-temporary-shared-storage'); ?></p>
+        
+        <div class="qdr-tss-form-actions">
+            <button type="button" class="button button-primary qdr-tss-confirm-delete-button"><?php esc_html_e('Delete', 'qdr-temporary-shared-storage'); ?></button>
+            <button type="button" class="button qdr-tss-modal-cancel"><?php esc_html_e('Cancel', 'qdr-temporary-shared-storage'); ?></button>
+        </div>
+        
+        <input type="hidden" id="qdr-tss-delete-id">
+    </div>
+</div>

+ 81 - 0
qdr-temporary-shared-storage/admin/partials/settings-page.php

@@ -0,0 +1,81 @@
+<?php
+/**
+ * Admin Settings Page
+ *
+ * @package QDR_Temporary_Shared_Storage
+ */
+
+// If this file is called directly, abort.
+if (!defined('WPINC')) {
+    die;
+}
+
+// Get current settings
+$settings = QDR_TSS_Settings::instance()->get_settings();
+$db_manager = QDR_TSS_DB_Manager::instance();
+
+// Get total storage usage
+$total_size = $db_manager->get_total_storage();
+$max_size = $settings['max_upload_size'];
+$usage_percent = ($max_size > 0) ? min(100, ($total_size / $max_size) * 100) : 0;
+?>
+
+<div class="wrap qdr-tss-admin">
+    <h1><?php esc_html_e('Shared Storage Settings', 'qdr-temporary-shared-storage'); ?></h1>
+    
+    <form id="qdr-tss-settings-form" method="post">
+        <table class="form-table">
+            <tbody>
+                <tr>
+                    <th scope="row">
+                        <label for="qdr-tss-api-key"><?php esc_html_e('API Key', 'qdr-temporary-shared-storage'); ?></label>
+                    </th>
+                    <td>
+                        <input type="text" id="qdr-tss-api-key" name="api_key" value="<?php echo esc_attr($settings['api_key']); ?>" class="regular-text">
+                        <p class="description"><?php esc_html_e('API key for REST API authentication.', 'qdr-temporary-shared-storage'); ?></p>
+                        <button type="button" id="qdr-tss-generate-key" class="button"><?php esc_html_e('Generate New Key', 'qdr-temporary-shared-storage'); ?></button>
+                    </td>
+                </tr>
+                
+                <tr>
+                    <th scope="row">
+                        <label for="qdr-tss-media-category"><?php esc_html_e('Media Category', 'qdr-temporary-shared-storage'); ?></label>
+                    </th>
+                    <td>
+                        <input type="text" id="qdr-tss-media-category" name="media_category" value="<?php echo esc_attr($settings['media_category']); ?>" class="regular-text">
+                        <p class="description"><?php esc_html_e('Category to assign to uploaded media.', 'qdr-temporary-shared-storage'); ?></p>
+                    </td>
+                </tr>
+                
+                <tr>
+                    <th scope="row">
+                        <label for="qdr-tss-max-upload-size"><?php esc_html_e('Maximum Storage Size (bytes)', 'qdr-temporary-shared-storage'); ?></label>
+                    </th>
+                    <td>
+                        <input type="number" id="qdr-tss-max-upload-size" name="max_upload_size" value="<?php echo esc_attr($settings['max_upload_size']); ?>" class="regular-text">
+                        <p class="description">
+                            <?php esc_html_e('Maximum total storage size in bytes. Current usage:', 'qdr-temporary-shared-storage'); ?> 
+                            <span id="qdr-tss-current-usage"><?php echo esc_html(qdr_tss_format_bytes($total_size)); ?></span>
+                            (<?php echo esc_html(qdr_tss_format_bytes($settings['max_upload_size'])); ?> <?php esc_html_e('max', 'qdr-temporary-shared-storage'); ?>)
+                        </p>
+                        <div class="qdr-tss-storage-bar">
+                            <div class="qdr-tss-storage-used" style="width: <?php echo min(100, ($total_size / $settings['max_upload_size']) * 100); ?>%"></div>
+                        </div>
+                        
+                        <p class="description">
+                            <?php esc_html_e('Common sizes:', 'qdr-temporary-shared-storage'); ?><br>
+                            1 GB = 1073741824 <?php esc_html_e('bytes', 'qdr-temporary-shared-storage'); ?><br>
+                            5 GB = 5368709120 <?php esc_html_e('bytes', 'qdr-temporary-shared-storage'); ?><br>
+                            10 GB = 10737418240 <?php esc_html_e('bytes', 'qdr-temporary-shared-storage'); ?>
+                        </p>
+                    </td>
+                </tr>
+            </tbody>
+        </table>
+        
+        <p class="submit">
+            <button type="submit" id="qdr-tss-save-settings" class="button button-primary"><?php esc_html_e('Save Settings', 'qdr-temporary-shared-storage'); ?></button>
+            <button type="button" id="qdr-tss-purge-expired" class="button"><?php esc_html_e('Purge Expired Files', 'qdr-temporary-shared-storage'); ?></button>
+        </p>
+    </form>
+</div>

+ 0 - 173
qdr-temporary-shared-storage/admin/settings-page.php

@@ -1,173 +0,0 @@
-<?php
-/**
- * Admin Settings Page
- *
- * @package QDR_Temporary_Shared_Storage
- */
-
-// If this file is called directly, abort.
-if (!defined('WPINC')) {
-    die;
-}
-
-/**
- * Render the Settings | Shared Storage admin page
- */
-function qdr_tss_render_settings_page() {
-    // Get current settings
-    $settings = get_option('qdr_tss_settings', array(
-        'api_key' => '',
-        'media_category' => 'shared-storage',
-        'max_upload_size' => 1073741824, // 1GB default
-    ));
-    
-    // Get total storage usage
-    global $wpdb;
-    $table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE;
-    $total_size = $wpdb->get_var("SELECT SUM(file_size) FROM $table_name");
-    $total_size = $total_size ? intval($total_size) : 0;
-    ?>
-    
-    <div class="wrap qdr-tss-admin">
-        <h1><?php esc_html_e('Shared Storage Settings', 'qdr-temporary-shared-storage'); ?></h1>
-        
-        <form id="qdr-tss-settings-form" method="post">
-            <table class="form-table">
-                <tbody>
-                    <tr>
-                        <th scope="row">
-                            <label for="qdr-tss-api-key"><?php esc_html_e('API Key', 'qdr-temporary-shared-storage'); ?></label>
-                        </th>
-                        <td>
-                            <input type="text" id="qdr-tss-api-key" name="api_key" value="<?php echo esc_attr($settings['api_key']); ?>" class="regular-text">
-                            <p class="description"><?php esc_html_e('API key for REST API authentication.', 'qdr-temporary-shared-storage'); ?></p>
-                            <button type="button" id="qdr-tss-generate-key" class="button"><?php esc_html_e('Generate New Key', 'qdr-temporary-shared-storage'); ?></button>
-                        </td>
-                    </tr>
-                    
-                    <tr>
-                        <th scope="row">
-                            <label for="qdr-tss-media-category"><?php esc_html_e('Media Category', 'qdr-temporary-shared-storage'); ?></label>
-                        </th>
-                        <td>
-                            <input type="text" id="qdr-tss-media-category" name="media_category" value="<?php echo esc_attr($settings['media_category']); ?>" class="regular-text">
-                            <p class="description"><?php esc_html_e('Category to assign to uploaded media.', 'qdr-temporary-shared-storage'); ?></p>
-                        </td>
-                    </tr>
-                    
-                    <tr>
-                        <th scope="row">
-                            <label for="qdr-tss-max-upload-size"><?php esc_html_e('Maximum Storage Size (bytes)', 'qdr-temporary-shared-storage'); ?></label>
-                        </th>
-                        <td>
-                            <input type="number" id="qdr-tss-max-upload-size" name="max_upload_size" value="<?php echo esc_attr($settings['max_upload_size']); ?>" class="regular-text">
-                            <p class="description">
-                                <?php esc_html_e('Maximum total storage size in bytes. Current usage:', 'qdr-temporary-shared-storage'); ?> 
-                                <span id="qdr-tss-current-usage"><?php echo esc_html(qdr_tss_format_bytes($total_size)); ?></span>
-                                (<?php echo esc_html(qdr_tss_format_bytes($settings['max_upload_size'])); ?> <?php esc_html_e('max', 'qdr-temporary-shared-storage'); ?>)
-                            </p>
-                            <div class="qdr-tss-storage-bar">
-                                <div class="qdr-tss-storage-used" style="width: <?php echo min(100, ($total_size / $settings['max_upload_size']) * 100); ?>%"></div>
-                            </div>
-                            
-                            <p class="description">
-                                <?php esc_html_e('Common sizes:', 'qdr-temporary-shared-storage'); ?><br>
-                                1 GB = 1073741824 <?php esc_html_e('bytes', 'qdr-temporary-shared-storage'); ?><br>
-                                5 GB = 5368709120 <?php esc_html_e('bytes', 'qdr-temporary-shared-storage'); ?><br>
-                                10 GB = 10737418240 <?php esc_html_e('bytes', 'qdr-temporary-shared-storage'); ?>
-                            </p>
-                        </td>
-                    </tr>
-                </tbody>
-            </table>
-            
-            <p class="submit">
-                <button type="submit" id="qdr-tss-save-settings" class="button button-primary"><?php esc_html_e('Save Settings', 'qdr-temporary-shared-storage'); ?></button>
-                <button type="button" id="qdr-tss-purge-expired" class="button"><?php esc_html_e('Purge Expired Files', 'qdr-temporary-shared-storage'); ?></button>
-            </p>
-        </form>
-    </div>
-    
-    <script type="text/javascript">
-        jQuery(document).ready(function($) {
-            // Generate new API key
-            $('#qdr-tss-generate-key').on('click', function(e) {
-                e.preventDefault();
-                
-                // Generate a random string for API key
-                var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
-                var apiKey = '';
-                for (var i = 0; i < 32; i++) {
-                    apiKey += chars.charAt(Math.floor(Math.random() * chars.length));
-                }
-                
-                $('#qdr-tss-api-key').val(apiKey);
-            });
-            
-            // Save settings
-            $('#qdr-tss-settings-form').on('submit', function(e) {
-                e.preventDefault();
-                
-                var data = {
-                    action: 'qdr_tss_save_settings',
-                    nonce: qdr_tss.nonce,
-                    api_key: $('#qdr-tss-api-key').val(),
-                    media_category: $('#qdr-tss-media-category').val(),
-                    max_upload_size: $('#qdr-tss-max-upload-size').val()
-                };
-                
-                $.post(ajaxurl, data, function(response) {
-                    if (response.success) {
-                        alert(response.data.message);
-                    } else {
-                        alert(response.data.message);
-                    }
-                });
-            });
-            
-            // Purge expired files
-            $('#qdr-tss-purge-expired').on('click', function(e) {
-                e.preventDefault();
-                
-                if (!confirm(qdr_tss.strings.confirm_purge)) {
-                    return;
-                }
-                
-                var data = {
-                    action: 'qdr_tss_purge_expired',
-                    nonce: qdr_tss.nonce
-                };
-                
-                $.post(ajaxurl, data, function(response) {
-                    if (response.success) {
-                        alert(response.data.message);
-                        // Reload page to update storage usage
-                        location.reload();
-                    } else {
-                        alert(response.data.message);
-                    }
-                });
-            });
-        });
-    </script>
-    <?php
-}
-
-/**
- * Format bytes to human readable format
- *
- * @param int $bytes
- * @param int $precision
- * @return string
- */
-function qdr_tss_format_bytes($bytes, $precision = 2) {
-    $units = array('B', 'KB', 'MB', 'GB', 'TB');
-    
-    $bytes = max($bytes, 0);
-    $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
-    $pow = min($pow, count($units) - 1);
-    
-    $bytes /= pow(1024, $pow);
-    
-    return round($bytes, $precision) . ' ' . $units[$pow];
-}

+ 0 - 84
qdr-temporary-shared-storage/assets/js/admin.js

@@ -1,84 +0,0 @@
-/**
- * Admin JavaScript for the QDR Temporary Shared Storage plugin
- */
-
-(function($) {
-    'use strict';
-
-    // Initialize datepickers on dynamically created elements
-    function initDatepickers() {
-        $('.qdr-tss-datepicker').datepicker({
-            dateFormat: 'yy-mm-dd',
-            changeMonth: true,
-            changeYear: true
-        });
-    }
-
-    // Format file size to human readable format
-    function formatFileSize(bytes) {
-        if (bytes === 0) return '0 Bytes';
-        const k = 1024;
-        const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
-        const i = Math.floor(Math.log(bytes) / Math.log(k));
-        return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
-    }
-
-    // Format date to local format
-    function formatDate(dateString) {
-        if (!dateString || dateString === '0000-00-00 00:00:00') {
-            return '-';
-        }
-        
-        var date = new Date(dateString);
-        
-        // Check if date is valid
-        if (isNaN(date.getTime())) {
-            return dateString;
-        }
-        
-        return date.toLocaleString();
-    }
-
-    // Set active sort indicators
-    function updateSortIndicators(orderBy, order) {
-        // Remove all indicators
-        $('.qdr-tss-table th').removeClass('sorted asc desc');
-        
-        // Add indicator to current sort column
-        var $th = $('.qdr-tss-table th[data-sort="' + orderBy + '"]');
-        $th.addClass('sorted ' + order.toLowerCase());
-    }
-
-    // Initialize tooltips
-    function initTooltips() {
-        $('.qdr-tss-tooltip').tooltip({
-            position: {
-                my: 'center bottom-20',
-                at: 'center top',
-                using: function(position, feedback) {
-                    $(this).css(position);
-                    $('<div>')
-                        .addClass('arrow')
-                        .addClass(feedback.vertical)
-                        .addClass(feedback.horizontal)
-                        .appendTo(this);
-                }
-            }
-        });
-    }
-
-    // Document ready
-    $(function() {
-        // Initialize datepickers on page load
-        initDatepickers();
-        
-        // Initialize tooltips
-        initTooltips();
-        
-        // Add button for refreshing the list
-        $('#qdr-tss-items-list').on('updated', function() {
-            initTooltips();
-        });
-    });
-
-})(jQuery);

+ 0 - 26
qdr-temporary-shared-storage/directory-structure.txt

@@ -1,26 +0,0 @@
-qdr-temporary-shared-storage/
-│
-├── qdr-temporary-shared-storage.php          # Main plugin file
-│
-├── admin/                                    # Admin pages
-│   ├── media-page.php                        # Media management interface
-│   └── settings-page.php                     # Plugin settings page
-│
-├── assets/                                   # Frontend and admin assets
-│   ├── css/
-│   │   └── admin.css                         # Admin styles
-│   └── js/
-│       └── admin.js                          # Admin JavaScript
-│
-├── languages/                                # Translations
-│   └── qdr-temporary-shared-storage.pot      # Translation template
-│
-├── readme.txt                                # WordPress.org plugin repository readme
-└── README.md                                 # GitHub/documentation readme
-
-Note: When uploading files, the plugin creates these directories:
-
-wp-content/uploads/qdr-shared-storage/        # Main storage directory
-├── .htaccess                                 # Protects direct access to files
-├── tmp/                                      # Temporary directory for chunked uploads
-└── YYYY/MM/DD/                               # Date-based folders for file storage

+ 67 - 0
qdr-temporary-shared-storage/includes/class-qdr-tss-activator.php

@@ -0,0 +1,67 @@
+<?php
+/**
+ * Fired during plugin activation.
+ *
+ * This class defines all code necessary to run during the plugin's activation.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+class QDR_TSS_Activator {
+
+    /**
+     * Activate the plugin.
+     *
+     * Creates necessary database tables, directories, and default settings.
+     *
+     * @since    1.0.0
+     */
+    public static function activate() {
+        // Create database table
+        require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-qdr-tss-db-manager.php';
+        $db_manager = QDR_TSS_DB_Manager::instance();
+        $db_manager->create_table();
+        
+        // Initialize file system
+        require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-qdr-tss-file-manager.php';
+        $file_manager = QDR_TSS_File_Manager::instance();
+        $file_manager->init_file_system();
+        
+        // Set default settings
+        require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-qdr-tss-settings.php';
+        $settings = QDR_TSS_Settings::instance();
+        
+        // Only set default settings if they don't exist
+        if (!get_option('qdr_tss_settings')) {
+            $default_settings = array(
+                'api_key' => $settings->generate_api_key(),
+                'media_category' => 'shared-storage',
+                'max_upload_size' => 1073741824, // 1GB default
+            );
+            $settings->update_settings($default_settings);
+        }
+        
+        // Create download page if it doesn't exist
+        $download_page = get_page_by_path('shared-file-download');
+        if (!$download_page) {
+            wp_insert_post(array(
+                'post_title' => __('Shared File Download', 'qdr-temporary-shared-storage'),
+                'post_content' => '[qdr_tss_download]',
+                'post_status' => 'publish',
+                'post_type' => 'page',
+                'post_name' => 'shared-file-download'
+            ));
+        }
+        
+        // Schedule cron job
+        if (!wp_next_scheduled('qdr_tss_purge_expired_files')) {
+            wp_schedule_event(time(), 'hourly', 'qdr_tss_purge_expired_files');
+        }
+        
+        // Set version number in database
+        update_option('qdr_tss_version', QDR_TSS_VERSION);
+        
+        // Clear permalinks
+        flush_rewrite_rules();
+    }
+}

+ 117 - 0
qdr-temporary-shared-storage/includes/class-qdr-tss-cron-manager.php

@@ -0,0 +1,117 @@
+<?php
+/**
+ * Cron operations for the plugin.
+ *
+ * This class handles all cron operations, including scheduling and executing
+ * tasks.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+class QDR_TSS_Cron_Manager {
+
+    /**
+     * The plugin name.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $plugin_name    The name of this plugin.
+     */
+    private $plugin_name;
+
+    /**
+     * The plugin version.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $version    The version of this plugin.
+     */
+    private $version;
+
+    /**
+     * Initialize the class and set its properties.
+     *
+     * @since    1.0.0
+     * @param    string    $plugin_name    The name of this plugin.
+     * @param    string    $version        The version of this plugin.
+     */
+    public function __construct($plugin_name, $version) {
+        $this->plugin_name = $plugin_name;
+        $this->version = $version;
+        
+        // Schedule the purge event if not scheduled
+        if (!wp_next_scheduled('qdr_tss_purge_expired_files')) {
+            wp_schedule_event(time(), 'hourly', 'qdr_tss_purge_expired_files');
+        }
+    }
+
+    /**
+     * Purge expired files.
+     *
+     * @since 1.0.0
+     * @return int Number of files purged.
+     */
+    public function purge_expired_files() {
+        // Get database manager
+        $db_manager = QDR_TSS_DB_Manager::instance();
+        
+        // Get file manager
+        $file_manager = QDR_TSS_File_Manager::instance();
+        
+        // Get expired items
+        $expired_items = $db_manager->get_expired_items();
+        
+        $purged_count = 0;
+        
+        // Delete files and database records
+        foreach ($expired_items as $item) {
+            // Delete file
+            $file_manager->delete_file($item->file_path);
+            
+            // Delete from database
+            $db_manager->delete_item($item->id);
+            
+            $purged_count++;
+        }
+        
+        // Clean up empty directories
+        $file_manager->cleanup_empty_directories();
+        
+        return $purged_count;
+    }
+
+    /**
+     * Manually purge expired files.
+     *
+     * @since 1.0.0
+     * @return int Number of files purged.
+     */
+    public function manual_purge_expired_files() {
+        return $this->purge_expired_files();
+    }
+
+    /**
+     * Schedule the purge event.
+     *
+     * @since 1.0.0
+     * @return void
+     */
+    public function schedule_purge_event() {
+        if (!wp_next_scheduled('qdr_tss_purge_expired_files')) {
+            wp_schedule_event(time(), 'hourly', 'qdr_tss_purge_expired_files');
+        }
+    }
+
+    /**
+     * Unschedule the purge event.
+     *
+     * @since 1.0.0
+     * @return void
+     */
+    public function unschedule_purge_event() {
+        $timestamp = wp_next_scheduled('qdr_tss_purge_expired_files');
+        if ($timestamp) {
+            wp_unschedule_event($timestamp, 'qdr_tss_purge_expired_files');
+        }
+    }
+}

+ 423 - 0
qdr-temporary-shared-storage/includes/class-qdr-tss-db-manager.php

@@ -0,0 +1,423 @@
+<?php
+/**
+ * Database operations for the plugin.
+ *
+ * This class handles all database operations, including creating tables,
+ * performing queries, and managing data.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+class QDR_TSS_DB_Manager {
+
+    /**
+     * The single instance of the class.
+     *
+     * @var QDR_TSS_DB_Manager
+     * @since 1.0.0
+     */
+    protected static $_instance = null;
+
+    /**
+     * The database table name.
+     *
+     * @var string
+     * @since 1.0.0
+     */
+    protected $table_name;
+
+    /**
+     * Main QDR_TSS_DB_Manager Instance.
+     *
+     * Ensures only one instance of QDR_TSS_DB_Manager is loaded or can be loaded.
+     *
+     * @since 1.0.0
+     * @static
+     * @return QDR_TSS_DB_Manager - Main instance.
+     */
+    public static function instance() {
+        if (is_null(self::$_instance)) {
+            self::$_instance = new self();
+        }
+        return self::$_instance;
+    }
+
+    /**
+     * QDR_TSS_DB_Manager Constructor.
+     */
+    public function __construct() {
+        global $wpdb;
+        $this->table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE;
+    }
+
+    /**
+     * Create the database table.
+     *
+     * @since 1.0.0
+     * @return bool True on success, false on failure.
+     */
+    public function create_table() {
+        global $wpdb;
+        
+        $charset_collate = $wpdb->get_charset_collate();
+        
+        $sql = "CREATE TABLE $this->table_name (
+            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)
+        ) $charset_collate;";
+        
+        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
+        return dbDelta($sql);
+    }
+
+    /**
+     * Get an item by ID.
+     *
+     * @since 1.0.0
+     * @param int $id The item ID.
+     * @return object|null Database row as object or null if not found.
+     */
+    public function get_item($id) {
+        global $wpdb;
+        
+        return $wpdb->get_row($wpdb->prepare("SELECT * FROM $this->table_name WHERE id = %d", $id));
+    }
+
+    /**
+     * Get an item by media ID.
+     *
+     * @since 1.0.0
+     * @param int $media_id The media ID.
+     * @return object|null Database row as object or null if not found.
+     */
+    public function get_item_by_media_id($media_id) {
+        global $wpdb;
+        
+        return $wpdb->get_row($wpdb->prepare("SELECT * FROM $this->table_name WHERE media_id = %d", $media_id));
+    }
+
+    /**
+     * Get items with pagination and filtering.
+     *
+     * @since 1.0.0
+     * @param array $args {
+     *     Optional. Arguments to retrieve items.
+     *
+     *     @type int    $per_page        Number of items per page. Default 10.
+     *     @type int    $page            Page number. Default 1.
+     *     @type string $orderby         Column to order by. Default 'upload_date'.
+     *     @type string $order           Order direction. Default 'DESC'.
+     *     @type string $original_file_name Filter by file name.
+     *     @type string $reference       Filter by reference.
+     * }
+     * @return array {
+     *     @type array  $items       The items.
+     *     @type int    $total       Total number of items.
+     *     @type int    $total_pages Total number of pages.
+     * }
+     */
+    public function get_items($args = array()) {
+        global $wpdb;
+        
+        // Default arguments
+        $defaults = array(
+            'per_page' => 10,
+            'page' => 1,
+            'orderby' => 'upload_date',
+            'order' => 'DESC',
+            'original_file_name' => '',
+            'reference' => ''
+        );
+        
+        $args = wp_parse_args($args, $defaults);
+        
+        // Ensure valid values
+        $args['per_page'] = absint($args['per_page']);
+        $args['page'] = absint($args['page']);
+        $args['order'] = strtoupper($args['order']) === 'ASC' ? 'ASC' : 'DESC';
+        
+        // Allowed columns for orderby
+        $allowed_orderby_columns = array(
+            'media_id', 'original_file_name', 'description', 'active_from', 'active_to',
+            'reference', 'upload_date', 'download_count', 'last_download'
+        );
+        
+        if (!in_array($args['orderby'], $allowed_orderby_columns)) {
+            $args['orderby'] = 'upload_date';
+        }
+        
+        // Build query
+        $query = "SELECT * FROM $this->table_name";
+        $count_query = "SELECT COUNT(*) FROM $this->table_name";
+        
+        $where_conditions = array();
+        $query_values = array();
+        
+        // Filter by original_file_name
+        if (!empty($args['original_file_name'])) {
+            $where_conditions[] = "original_file_name LIKE %s";
+            $query_values[] = '%' . $wpdb->esc_like($args['original_file_name']) . '%';
+        }
+        
+        // Filter by reference
+        if (!empty($args['reference'])) {
+            $where_conditions[] = "reference LIKE %s";
+            $query_values[] = '%' . $wpdb->esc_like($args['reference']) . '%';
+        }
+        
+        // Add WHERE conditions if any
+        if (!empty($where_conditions)) {
+            $where_clause = " WHERE " . implode(" AND ", $where_conditions);
+            $query .= $where_clause;
+            $count_query .= $where_clause;
+        }
+        
+        // Add ordering
+        $query .= " ORDER BY {$args['orderby']} {$args['order']}";
+        
+        // Add pagination
+        $offset = ($args['page'] - 1) * $args['per_page'];
+        $query .= " LIMIT %d OFFSET %d";
+        $query_values[] = $args['per_page'];
+        $query_values[] = $offset;
+        
+        // Prepare and execute queries
+        $items = $wpdb->get_results($wpdb->prepare($query, $query_values));
+        
+        $total_query_values = array_slice($query_values, 0, -2);
+        $total = $wpdb->get_var($wpdb->prepare($count_query, $total_query_values));
+        
+        return array(
+            'items' => $items,
+            'total' => (int) $total,
+            'total_pages' => ceil($total / $args['per_page'])
+        );
+    }
+
+    /**
+     * Insert a new item.
+     *
+     * @since 1.0.0
+     * @param array $data The item data.
+     * @return int|false The item ID on success, false on failure.
+     */
+    public function insert_item($data) {
+        global $wpdb;
+        
+        $result = $wpdb->insert(
+            $this->table_name,
+            $data,
+            $this->get_column_formats($data)
+        );
+        
+        return $result ? $wpdb->insert_id : false;
+    }
+
+    /**
+     * Update an existing item.
+     *
+     * @since 1.0.0
+     * @param int   $id   The item ID.
+     * @param array $data The item data.
+     * @return bool True on success, false on failure.
+     */
+    public function update_item($id, $data) {
+        global $wpdb;
+        
+        return $wpdb->update(
+            $this->table_name,
+            $data,
+            array('id' => $id),
+            $this->get_column_formats($data),
+            array('%d')
+        );
+    }
+
+    /**
+     * Update an item by media ID.
+     *
+     * @since 1.0.0
+     * @param int   $media_id The media ID.
+     * @param array $data     The item data.
+     * @return bool True on success, false on failure.
+     */
+    public function update_item_by_media_id($media_id, $data) {
+        global $wpdb;
+        
+        return $wpdb->update(
+            $this->table_name,
+            $data,
+            array('media_id' => $media_id),
+            $this->get_column_formats($data),
+            array('%d')
+        );
+    }
+
+    /**
+     * Delete an item.
+     *
+     * @since 1.0.0
+     * @param int $id The item ID.
+     * @return bool True on success, false on failure.
+     */
+    public function delete_item($id) {
+        global $wpdb;
+        
+        return $wpdb->delete(
+            $this->table_name,
+            array('id' => $id),
+            array('%d')
+        );
+    }
+
+    /**
+     * Delete an item by media ID.
+     *
+     * @since 1.0.0
+     * @param int $media_id The media ID.
+     * @return bool True on success, false on failure.
+     */
+    public function delete_item_by_media_id($media_id) {
+        global $wpdb;
+        
+        return $wpdb->delete(
+            $this->table_name,
+            array('media_id' => $media_id),
+            array('%d')
+        );
+    }
+
+    /**
+     * Get expired items.
+     *
+     * @since 1.0.0
+     * @return array Array of expired items.
+     */
+    public function get_expired_items() {
+        global $wpdb;
+        
+        $current_time = current_time('mysql');
+        
+        return $wpdb->get_results($wpdb->prepare(
+            "SELECT * FROM $this->table_name WHERE active_to < %s",
+            $current_time
+        ));
+    }
+
+    /**
+     * Delete expired items.
+     *
+     * @since 1.0.0
+     * @return int Number of deleted items.
+     */
+    public function delete_expired_items() {
+        global $wpdb;
+        
+        $current_time = current_time('mysql');
+        
+        return $wpdb->query($wpdb->prepare(
+            "DELETE FROM $this->table_name WHERE active_to < %s",
+            $current_time
+        ));
+    }
+
+    /**
+     * Increment download count and update last download time.
+     *
+     * @since 1.0.0
+     * @param int $id The item ID.
+     * @return bool True on success, false on failure.
+     */
+    public function increment_download_count($id) {
+        global $wpdb;
+        
+        return $wpdb->query($wpdb->prepare(
+            "UPDATE $this->table_name SET download_count = download_count + 1, last_download = %s WHERE id = %d",
+            current_time('mysql'),
+            $id
+        ));
+    }
+
+    /**
+     * Calculate total storage usage.
+     *
+     * @since 1.0.0
+     * @return int Total storage usage in bytes.
+     */
+    public function get_total_storage() {
+        global $wpdb;
+        
+        $total = $wpdb->get_var("SELECT SUM(file_size) FROM $this->table_name");
+        
+        return $total ? (int) $total : 0;
+    }
+
+    /**
+     * Check if adding a new file would exceed storage limit.
+     *
+     * @since 1.0.0
+     * @param int $file_size The file size in bytes.
+     * @param int $max_size  The maximum allowed storage size in bytes.
+     * @return bool True if adding the file would exceed limit, false otherwise.
+     */
+    public function would_exceed_storage_limit($file_size, $max_size) {
+        $current_usage = $this->get_total_storage();
+        
+        return ($current_usage + $file_size) > $max_size;
+    }
+
+    /**
+     * Get column formats for data.
+     *
+     * @since 1.0.0
+     * @access private
+     * @param array $data The data to get formats for.
+     * @return array The column formats.
+     */
+    private function get_column_formats($data) {
+        $formats = array();
+        
+        $columns = array(
+            'id' => '%d',
+            'media_id' => '%d',
+            'original_file_name' => '%s',
+            'description' => '%s',
+            'message' => '%s',
+            'active_from' => '%s',
+            'active_to' => '%s',
+            'password' => '%s',
+            'reference' => '%s',
+            'upload_date' => '%s',
+            'download_count' => '%d',
+            'last_download' => '%s',
+            'file_path' => '%s',
+            'file_size' => '%d'
+        );
+        
+        foreach ($data as $column => $value) {
+            if (isset($columns[$column])) {
+                $formats[] = $columns[$column];
+            } else {
+                $formats[] = '%s';
+            }
+        }
+        
+        return $formats;
+    }
+}

+ 29 - 0
qdr-temporary-shared-storage/includes/class-qdr-tss-deactivator.php

@@ -0,0 +1,29 @@
+<?php
+/**
+ * Fired during plugin deactivation.
+ *
+ * This class defines all code necessary to run during the plugin's deactivation.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+class QDR_TSS_Deactivator {
+
+    /**
+     * Deactivate the plugin.
+     *
+     * Clears scheduled tasks and flushes rewrite rules.
+     *
+     * @since    1.0.0
+     */
+    public static function deactivate() {
+        // Clear scheduled cron job
+        $timestamp = wp_next_scheduled('qdr_tss_purge_expired_files');
+        if ($timestamp) {
+            wp_unschedule_event($timestamp, 'qdr_tss_purge_expired_files');
+        }
+        
+        // Clear permalinks
+        flush_rewrite_rules();
+    }
+}

+ 352 - 0
qdr-temporary-shared-storage/includes/class-qdr-tss-file-manager.php

@@ -0,0 +1,352 @@
+<?php
+/**
+ * File operations for the plugin.
+ *
+ * This class handles all file operations, including creating directories,
+ * uploading files, chunked uploads, and deleting files.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+class QDR_TSS_File_Manager {
+
+    /**
+     * The single instance of the class.
+     *
+     * @var QDR_TSS_File_Manager
+     * @since 1.0.0
+     */
+    protected static $_instance = null;
+
+    /**
+     * Main QDR_TSS_File_Manager Instance.
+     *
+     * Ensures only one instance of QDR_TSS_File_Manager is loaded or can be loaded.
+     *
+     * @since 1.0.0
+     * @static
+     * @return QDR_TSS_File_Manager - Main instance.
+     */
+    public static function instance() {
+        if (is_null(self::$_instance)) {
+            self::$_instance = new self();
+        }
+        return self::$_instance;
+    }
+
+    /**
+     * QDR_TSS_File_Manager Constructor.
+     */
+    public function __construct() {
+        // Initialize file system if needed
+        $this->init_file_system();
+    }
+
+    /**
+     * Initialize file system directories.
+     *
+     * @since 1.0.0
+     * @return bool True on success, false on failure.
+     */
+    public function init_file_system() {
+        // Create base upload directory if it doesn't exist
+        if (!file_exists(QDR_TSS_UPLOADS_DIR)) {
+            if (!wp_mkdir_p(QDR_TSS_UPLOADS_DIR)) {
+                return false;
+            }
+            
+            // Create .htaccess file to protect direct access
+            $htaccess_content = "# Disable directory browsing\nOptions -Indexes\n\n# Deny direct access to all files\n<FilesMatch \".*\">\nOrder Allow,Deny\nDeny from all\n</FilesMatch>";
+            file_put_contents(QDR_TSS_UPLOADS_DIR . '.htaccess', $htaccess_content);
+        }
+        
+        // Create temporary directory for chunked uploads
+        if (!file_exists(QDR_TSS_UPLOADS_DIR . 'tmp/')) {
+            if (!wp_mkdir_p(QDR_TSS_UPLOADS_DIR . 'tmp/')) {
+                return false;
+            }
+        }
+        
+        return true;
+    }
+
+    /**
+     * Create a dated directory for storing files.
+     *
+     * @since 1.0.0
+     * @param string $date Optional. Date string in Y-m-d format. Default is current date.
+     * @return string|false Path to the directory or false on failure.
+     */
+    public function create_dated_directory($date = '') {
+        if (empty($date)) {
+            $date = date('Y/m/d');
+        }
+        
+        $directory = QDR_TSS_UPLOADS_DIR . $date;
+        
+        if (!file_exists($directory)) {
+            if (!wp_mkdir_p($directory)) {
+                return false;
+            }
+        }
+        
+        return $directory;
+    }
+
+    /**
+     * Initialize a chunked upload.
+     *
+     * @since 1.0.0
+     * @return string|false The upload ID or false on failure.
+     */
+    public function init_chunked_upload() {
+        $upload_id = uniqid();
+        $upload_dir = QDR_TSS_UPLOADS_DIR . 'tmp/' . $upload_id;
+        
+        if (!wp_mkdir_p($upload_dir)) {
+            return false;
+        }
+        
+        return $upload_id;
+    }
+
+    /**
+     * Store a chunk of a file.
+     *
+     * @since 1.0.0
+     * @param string $upload_id   The upload ID.
+     * @param int    $chunk_index The chunk index.
+     * @param array  $file        The chunk file data from $_FILES.
+     * @return bool True on success, false on failure.
+     */
+    public function store_chunk($upload_id, $chunk_index, $file) {
+        $upload_dir = QDR_TSS_UPLOADS_DIR . 'tmp/' . $upload_id;
+        
+        // Check if the upload directory exists
+        if (!file_exists($upload_dir)) {
+            return false;
+        }
+        
+        // Check if file was uploaded successfully
+        if (!isset($file['tmp_name']) || !is_uploaded_file($file['tmp_name'])) {
+            return false;
+        }
+        
+        // Move uploaded chunk to temporary directory
+        $chunk_path = $upload_dir . '/' . $chunk_index;
+        return move_uploaded_file($file['tmp_name'], $chunk_path);
+    }
+
+    /**
+     * Check if all chunks are present.
+     *
+     * @since 1.0.0
+     * @param string $upload_id    The upload ID.
+     * @param int    $total_chunks The total number of chunks.
+     * @return bool True if all chunks are present, false otherwise.
+     */
+    public function check_all_chunks($upload_id, $total_chunks) {
+        $upload_dir = QDR_TSS_UPLOADS_DIR . 'tmp/' . $upload_id;
+        
+        // Check if the upload directory exists
+        if (!file_exists($upload_dir)) {
+            return false;
+        }
+        
+        // Check that all chunks are present
+        for ($i = 0; $i < $total_chunks; $i++) {
+            if (!file_exists($upload_dir . '/' . $i)) {
+                return false;
+            }
+        }
+        
+        return true;
+    }
+
+    /**
+     * Finalize a chunked upload.
+     *
+     * @since 1.0.0
+     * @param string $upload_id        The upload ID.
+     * @param int    $total_chunks     The total number of chunks.
+     * @param string $original_filename The original file name.
+     * @return array|false {
+     *     @type string $file_path The path to the combined file.
+     *     @type int    $file_size The size of the combined file.
+     * } or false on failure.
+     */
+    public function finalize_chunked_upload($upload_id, $total_chunks, $original_filename) {
+        $upload_dir = QDR_TSS_UPLOADS_DIR . 'tmp/' . $upload_id;
+        
+        // Check if the upload directory exists
+        if (!file_exists($upload_dir)) {
+            return false;
+        }
+        
+        // Check that all chunks are present
+        if (!$this->check_all_chunks($upload_id, $total_chunks)) {
+            return false;
+        }
+        
+        // Create final file directory
+        $file_dir = $this->create_dated_directory();
+        if (!$file_dir) {
+            return false;
+        }
+        
+        // Sanitize file name
+        $original_filename = sanitize_file_name($original_filename);
+        $file_name = md5($original_filename . time()) . '-' . $original_filename;
+        $file_path = $file_dir . '/' . $file_name;
+        
+        // Combine chunks
+        if (!$this->combine_chunks($upload_dir, $total_chunks, $file_path)) {
+            return false;
+        }
+        
+        // Clean up temporary directory
+        $this->cleanup_chunks($upload_dir);
+        
+        // Get file size
+        $file_size = filesize($file_path);
+        
+        return array(
+            'file_path' => str_replace(QDR_TSS_UPLOADS_DIR, '', $file_path),
+            'file_size' => $file_size
+        );
+    }
+
+    /**
+     * Combine chunks into a single file.
+     *
+     * @since 1.0.0
+     * @access private
+     * @param string $upload_dir    The upload directory.
+     * @param int    $total_chunks  The total number of chunks.
+     * @param string $file_path     The path to the combined file.
+     * @return bool True on success, false on failure.
+     */
+    private function combine_chunks($upload_dir, $total_chunks, $file_path) {
+        // Create final file
+        $final_file = fopen($file_path, 'wb');
+        if (!$final_file) {
+            return false;
+        }
+        
+        // Combine chunks
+        for ($i = 0; $i < $total_chunks; $i++) {
+            $chunk_path = $upload_dir . '/' . $i;
+            $chunk = fopen($chunk_path, 'rb');
+            if (!$chunk) {
+                fclose($final_file);
+                unlink($file_path);
+                return false;
+            }
+            
+            stream_copy_to_stream($chunk, $final_file);
+            fclose($chunk);
+        }
+        
+        fclose($final_file);
+        return true;
+    }
+
+    /**
+     * Clean up chunks after successful upload.
+     *
+     * @since 1.0.0
+     * @access private
+     * @param string $upload_dir The upload directory.
+     * @return bool True on success, false on failure.
+     */
+    private function cleanup_chunks($upload_dir) {
+        // Delete all chunk files
+        $files = glob($upload_dir . '/*');
+        foreach ($files as $file) {
+            if (is_file($file)) {
+                unlink($file);
+            }
+        }
+        
+        // Delete upload directory
+        return rmdir($upload_dir);
+    }
+
+    /**
+     * Delete a file.
+     *
+     * @since 1.0.0
+     * @param string $file_path The file path relative to the upload directory.
+     * @return bool True on success, false on failure.
+     */
+    public function delete_file($file_path) {
+        $full_path = QDR_TSS_UPLOADS_DIR . $file_path;
+        
+        if (file_exists($full_path)) {
+            return unlink($full_path);
+        }
+        
+        return false;
+    }
+
+    /**
+     * Get file size.
+     *
+     * @since 1.0.0
+     * @param string $file_path The file path relative to the upload directory.
+     * @return int|false The file size in bytes or false if file doesn't exist.
+     */
+    public function get_file_size($file_path) {
+        $full_path = QDR_TSS_UPLOADS_DIR . $file_path;
+        
+        if (file_exists($full_path)) {
+            return filesize($full_path);
+        }
+        
+        return false;
+    }
+
+    /**
+     * Clean up empty directories.
+     *
+     * @since 1.0.0
+     * @param string $directory The directory to clean.
+     * @return void
+     */
+    public function cleanup_empty_directories($directory = '') {
+        if (empty($directory)) {
+            $directory = QDR_TSS_UPLOADS_DIR;
+        }
+        
+        // Skip special directories
+        if (basename($directory) === 'tmp') {
+            return;
+        }
+        
+        // Skip if not a directory
+        if (!is_dir($directory)) {
+            return;
+        }
+        
+        // Get all files and directories
+        $files = scandir($directory);
+        $files = array_diff($files, array('.', '..', '.htaccess'));
+        
+        // Process subdirectories first
+        foreach ($files as $file) {
+            $path = $directory . '/' . $file;
+            if (is_dir($path)) {
+                $this->cleanup_empty_directories($path);
+            }
+        }
+        
+        // Check if directory is empty now
+        $remaining_files = scandir($directory);
+        $remaining_files = array_diff($remaining_files, array('.', '..', '.htaccess'));
+        
+        // Remove directory if empty
+        if (empty($remaining_files)) {
+            @rmdir($directory);
+        }
+    }
+}

+ 25 - 0
qdr-temporary-shared-storage/includes/class-qdr-tss-i18n.php

@@ -0,0 +1,25 @@
+<?php
+/**
+ * Define the internationalization functionality.
+ *
+ * Loads and defines the internationalization files for this plugin
+ * so that it is ready for translation.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+class QDR_TSS_i18n {
+
+    /**
+     * Load the plugin text domain for translation.
+     *
+     * @since    1.0.0
+     */
+    public function load_plugin_textdomain() {
+        load_plugin_textdomain(
+            'qdr-temporary-shared-storage',
+            false,
+            dirname(dirname(plugin_basename(__FILE__))) . '/languages/'
+        );
+    }
+}

+ 110 - 0
qdr-temporary-shared-storage/includes/class-qdr-tss-loader.php

@@ -0,0 +1,110 @@
+<?php
+/**
+ * Register all actions and filters for the plugin.
+ *
+ * Maintain a list of all hooks that are registered throughout
+ * the plugin, and register them with the WordPress API. Call the
+ * run function to execute the list of actions and filters.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+class QDR_TSS_Loader {
+
+    /**
+     * The array of actions registered with WordPress.
+     *
+     * @since    1.0.0
+     * @access   protected
+     * @var      array    $actions    The actions registered with WordPress to fire when the plugin loads.
+     */
+    protected $actions;
+
+    /**
+     * The array of filters registered with WordPress.
+     *
+     * @since    1.0.0
+     * @access   protected
+     * @var      array    $filters    The filters registered with WordPress to fire when the plugin loads.
+     */
+    protected $filters;
+
+    /**
+     * Initialize the collections used to maintain the actions and filters.
+     *
+     * @since    1.0.0
+     */
+    public function __construct() {
+        $this->actions = array();
+        $this->filters = array();
+    }
+
+    /**
+     * Add a new action to the collection to be registered with WordPress.
+     *
+     * @since    1.0.0
+     * @param    string               $hook             The name of the WordPress action that is being registered.
+     * @param    object               $component        A reference to the instance of the object on which the action is defined.
+     * @param    string               $callback         The name of the function definition on the $component.
+     * @param    int                  $priority         Optional. The priority at which the function should be fired. Default is 10.
+     * @param    int                  $accepted_args    Optional. The number of arguments that should be passed to the $callback. Default is 1.
+     */
+    public function add_action($hook, $component, $callback, $priority = 10, $accepted_args = 1) {
+        $this->actions = $this->add($this->actions, $hook, $component, $callback, $priority, $accepted_args);
+    }
+
+    /**
+     * Add a new filter to the collection to be registered with WordPress.
+     *
+     * @since    1.0.0
+     * @param    string               $hook             The name of the WordPress filter that is being registered.
+     * @param    object               $component        A reference to the instance of the object on which the filter is defined.
+     * @param    string               $callback         The name of the function definition on the $component.
+     * @param    int                  $priority         Optional. The priority at which the function should be fired. Default is 10.
+     * @param    int                  $accepted_args    Optional. The number of arguments that should be passed to the $callback. Default is 1.
+     */
+    public function add_filter($hook, $component, $callback, $priority = 10, $accepted_args = 1) {
+        $this->filters = $this->add($this->filters, $hook, $component, $callback, $priority, $accepted_args);
+    }
+
+    /**
+     * A utility function that is used to register the actions and hooks into a single
+     * collection.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @param    array                $hooks            The collection of hooks that is being registered (that is, actions or filters).
+     * @param    string               $hook             The name of the WordPress filter that is being registered.
+     * @param    object               $component        A reference to the instance of the object on which the filter is defined.
+     * @param    string               $callback         The name of the function definition on the $component.
+     * @param    int                  $priority         The priority at which the function should be fired.
+     * @param    int                  $accepted_args    The number of arguments that should be passed to the $callback.
+     * @return   array                                  The collection of actions and filters registered with WordPress.
+     */
+    private function add($hooks, $hook, $component, $callback, $priority, $accepted_args) {
+        $hooks[] = array(
+            'hook'          => $hook,
+            'component'     => $component,
+            'callback'      => $callback,
+            'priority'      => $priority,
+            'accepted_args' => $accepted_args
+        );
+
+        return $hooks;
+    }
+
+    /**
+     * Register the filters and actions with WordPress.
+     *
+     * @since    1.0.0
+     */
+    public function run() {
+        foreach ($this->filters as $hook) {
+            add_filter($hook['hook'], array($hook['component'], $hook['callback']), $hook['priority'], $hook['accepted_args']);
+        }
+
+        foreach ($this->actions as $hook) {
+            add_action($hook['hook'], array($hook['component'], $hook['callback']), $hook['priority'], $hook['accepted_args']);
+        }
+    }
+}

+ 186 - 0
qdr-temporary-shared-storage/includes/class-qdr-tss-settings.php

@@ -0,0 +1,186 @@
+<?php
+/**
+ * Settings operations for the plugin.
+ *
+ * This class handles all settings operations, including getting and setting
+ * plugin options.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+class QDR_TSS_Settings {
+
+    /**
+     * The single instance of the class.
+     *
+     * @var QDR_TSS_Settings
+     * @since 1.0.0
+     */
+    protected static $_instance = null;
+
+    /**
+     * Option name for settings.
+     *
+     * @var string
+     * @since 1.0.0
+     */
+    private $option_name = 'qdr_tss_settings';
+
+    /**
+     * Default settings.
+     *
+     * @var array
+     * @since 1.0.0
+     */
+    private $defaults = array(
+        'api_key' => '',
+        'media_category' => 'shared-storage',
+        'max_upload_size' => 1073741824, // 1GB default
+    );
+
+    /**
+     * Current settings.
+     *
+     * @var array
+     * @since 1.0.0
+     */
+    private $settings = array();
+
+    /**
+     * Main QDR_TSS_Settings Instance.
+     *
+     * Ensures only one instance of QDR_TSS_Settings is loaded or can be loaded.
+     *
+     * @since 1.0.0
+     * @static
+     * @return QDR_TSS_Settings - Main instance.
+     */
+    public static function instance() {
+        if (is_null(self::$_instance)) {
+            self::$_instance = new self();
+        }
+        return self::$_instance;
+    }
+
+    /**
+     * QDR_TSS_Settings Constructor.
+     */
+    public function __construct() {
+        $this->load_settings();
+    }
+
+    /**
+     * Load settings from database.
+     *
+     * @since 1.0.0
+     */
+    public function load_settings() {
+        $settings = get_option($this->option_name, array());
+        
+        // Generate API key if it doesn't exist
+        if (empty($settings['api_key'])) {
+            $settings['api_key'] = $this->generate_api_key();
+        }
+        
+        $this->settings = wp_parse_args($settings, $this->defaults);
+    }
+
+    /**
+     * Get all settings.
+     *
+     * @since 1.0.0
+     * @return array The settings.
+     */
+    public function get_settings() {
+        return $this->settings;
+    }
+
+    /**
+     * Get a specific setting.
+     *
+     * @since 1.0.0
+     * @param string $key     The setting key.
+     * @param mixed  $default The default value if setting doesn't exist.
+     * @return mixed The setting value.
+     */
+    public function get_setting($key, $default = null) {
+        if (isset($this->settings[$key])) {
+            return $this->settings[$key];
+        }
+        
+        return $default !== null ? $default : (isset($this->defaults[$key]) ? $this->defaults[$key] : null);
+    }
+
+    /**
+     * Update settings.
+     *
+     * @since 1.0.0
+     * @param array $settings The settings to update.
+     * @return bool True on success, false on failure.
+     */
+    public function update_settings($settings) {
+        // Merge with existing settings
+        $this->settings = wp_parse_args($settings, $this->settings);
+        
+        // Save to database
+        $result = update_option($this->option_name, $this->settings);
+        
+        return $result;
+    }
+
+    /**
+     * Update a specific setting.
+     *
+     * @since 1.0.0
+     * @param string $key   The setting key.
+     * @param mixed  $value The setting value.
+     * @return bool True on success, false on failure.
+     */
+    public function update_setting($key, $value) {
+        $this->settings[$key] = $value;
+        
+        // Save to database
+        return update_option($this->option_name, $this->settings);
+    }
+
+    /**
+     * Generate a random API key.
+     *
+     * @since 1.0.0
+     * @return string The generated API key.
+     */
+    public function generate_api_key() {
+        return wp_generate_password(32, false);
+    }
+
+    /**
+     * Validate API key.
+     *
+     * @since 1.0.0
+     * @param string $api_key The API key to validate.
+     * @return bool True if valid, false otherwise.
+     */
+    public function validate_api_key($api_key) {
+        return $api_key === $this->get_setting('api_key');
+    }
+
+    /**
+     * Format bytes to human readable format.
+     *
+     * @since 1.0.0
+     * @param int $bytes     The size in bytes.
+     * @param int $precision The number of decimal places.
+     * @return string Human readable size.
+     */
+    public function format_bytes($bytes, $precision = 2) {
+        $units = array('B', 'KB', 'MB', 'GB', 'TB');
+        
+        $bytes = max($bytes, 0);
+        $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
+        $pow = min($pow, count($units) - 1);
+        
+        $bytes /= pow(1024, $pow);
+        
+        return round($bytes, $precision) . ' ' . $units[$pow];
+    }
+}

+ 290 - 0
qdr-temporary-shared-storage/includes/class-qdr-tss.php

@@ -0,0 +1,290 @@
+<?php
+/**
+ * The core plugin class.
+ *
+ * This is used to define internationalization, admin-specific hooks, and
+ * public-facing site hooks.
+ *
+ * @since      1.0.0
+ * @package    QDR_Temporary_Shared_Storage
+ */
+
+class QDR_TSS {
+
+    /**
+     * The loader that's responsible for maintaining and registering all hooks that power
+     * the plugin.
+     *
+     * @since    1.0.0
+     * @access   protected
+     * @var      QDR_TSS_Loader    $loader    Maintains and registers all hooks for the plugin.
+     */
+    protected $loader;
+
+    /**
+     * The unique identifier of this plugin.
+     *
+     * @since    1.0.0
+     * @access   protected
+     * @var      string    $plugin_name    The string used to uniquely identify this plugin.
+     */
+    protected $plugin_name;
+
+    /**
+     * The current version of the plugin.
+     *
+     * @since    1.0.0
+     * @access   protected
+     * @var      string    $version    The current version of the plugin.
+     */
+    protected $version;
+
+    /**
+     * Define the core functionality of the plugin.
+     *
+     * Set the plugin name and the plugin version that can be used throughout the plugin.
+     * Load the dependencies, define the locale, and set the hooks for the admin area and
+     * the public-facing side of the site.
+     *
+     * @since    1.0.0
+     */
+    public function __construct() {
+        $this->version = QDR_TSS_VERSION;
+        $this->plugin_name = 'qdr-temporary-shared-storage';
+        
+        $this->define_constants();
+        $this->load_dependencies();
+        $this->set_locale();
+        $this->define_admin_hooks();
+        $this->define_public_hooks();
+        $this->define_rest_api_hooks();
+        $this->define_cron_hooks();
+    }
+
+    /**
+     * Define constants used by the plugin.
+     *
+     * @since    1.0.0
+     * @access   private
+     */
+    private function define_constants() {
+        define('QDR_TSS_PLUGIN_DIR', plugin_dir_path(dirname(__FILE__)));
+        define('QDR_TSS_PLUGIN_URL', plugin_dir_url(dirname(__FILE__)));
+        define('QDR_TSS_PLUGIN_BASENAME', plugin_basename(dirname(__FILE__)));
+        define('QDR_TSS_UPLOADS_DIR', wp_upload_dir()['basedir'] . '/qdr-shared-storage/');
+        define('QDR_TSS_UPLOADS_URL', wp_upload_dir()['baseurl'] . '/qdr-shared-storage/');
+        define('QDR_TSS_PLUGIN_TABLE', 'qdr_temporary_shared_storage');
+    }
+
+    /**
+     * Load the required dependencies for this plugin.
+     *
+     * Include the following files that make up the plugin:
+     *
+     * - QDR_TSS_Loader. Orchestrates the hooks of the plugin.
+     * - QDR_TSS_i18n. Defines internationalization functionality.
+     * - QDR_TSS_Admin. Defines all hooks for the admin area.
+     * - QDR_TSS_Public. Defines all hooks for the public side of the site.
+     * - QDR_TSS_REST_Controller. Defines all hooks for the REST API.
+     * - QDR_TSS_DB_Manager. Handles database operations.
+     * - QDR_TSS_File_Manager. Handles file operations.
+     * - QDR_TSS_Settings. Handles plugin settings.
+     * - QDR_TSS_Cron_Manager. Handles scheduled tasks.
+     *
+     * @since    1.0.0
+     * @access   private
+     */
+    private function load_dependencies() {
+        /**
+         * The class responsible for orchestrating the actions and filters of the
+         * core plugin.
+         */
+        require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-qdr-tss-loader.php';
+
+        /**
+         * The class responsible for defining internationalization functionality
+         * of the plugin.
+         */
+        require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-qdr-tss-i18n.php';
+
+        /**
+         * The class responsible for defining all database operations.
+         */
+        require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-qdr-tss-db-manager.php';
+
+        /**
+         * The class responsible for defining all file operations.
+         */
+        require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-qdr-tss-file-manager.php';
+
+        /**
+         * The class responsible for handling plugin settings.
+         */
+        require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-qdr-tss-settings.php';
+
+        /**
+         * The class responsible for handling scheduled tasks.
+         */
+        require_once plugin_dir_path(dirname(__FILE__)) . 'includes/class-qdr-tss-cron-manager.php';
+        
+        /**
+         * Helper functions.
+         */
+        require_once plugin_dir_path(dirname(__FILE__)) . 'includes/functions.php';
+
+        /**
+         * The class responsible for defining all actions that occur in the admin area.
+         */
+        require_once plugin_dir_path(dirname(__FILE__)) . 'admin/class-qdr-tss-admin.php';
+        require_once plugin_dir_path(dirname(__FILE__)) . 'admin/class-qdr-tss-media-page.php';
+        require_once plugin_dir_path(dirname(__FILE__)) . 'admin/class-qdr-tss-settings-page.php';
+
+        /**
+         * The class responsible for defining all actions that occur in the public-facing
+         * side of the site.
+         */
+        require_once plugin_dir_path(dirname(__FILE__)) . 'public/class-qdr-tss-public.php';
+        require_once plugin_dir_path(dirname(__FILE__)) . 'public/class-qdr-tss-shortcode.php';
+        require_once plugin_dir_path(dirname(__FILE__)) . 'public/class-qdr-tss-download.php';
+
+        /**
+         * The class responsible for defining all REST API functionality.
+         */
+        require_once plugin_dir_path(dirname(__FILE__)) . 'rest-api/class-qdr-tss-rest-controller.php';
+        require_once plugin_dir_path(dirname(__FILE__)) . 'rest-api/class-qdr-tss-media-controller.php';
+        require_once plugin_dir_path(dirname(__FILE__)) . 'rest-api/class-qdr-tss-upload-controller.php';
+
+        $this->loader = new QDR_TSS_Loader();
+    }
+
+    /**
+     * Define the locale for this plugin for internationalization.
+     *
+     * Uses the QDR_TSS_i18n class in order to set the domain and to register the hook
+     * with WordPress.
+     *
+     * @since    1.0.0
+     * @access   private
+     */
+    private function set_locale() {
+        $plugin_i18n = new QDR_TSS_i18n();
+        $this->loader->add_action('plugins_loaded', $plugin_i18n, 'load_plugin_textdomain');
+    }
+
+    /**
+     * Register all of the hooks related to the admin area functionality
+     * of the plugin.
+     *
+     * @since    1.0.0
+     * @access   private
+     */
+    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());
+
+        // Admin hooks
+        $this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_styles');
+        $this->loader->add_action('admin_enqueue_scripts', $plugin_admin, 'enqueue_scripts');
+        $this->loader->add_action('admin_menu', $plugin_admin, 'add_admin_menu');
+
+        // AJAX handlers
+        $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_save_settings', $settings_page, 'ajax_save_settings');
+        $this->loader->add_action('wp_ajax_qdr_tss_purge_expired', $settings_page, 'ajax_purge_expired');
+    }
+
+    /**
+     * Register all of the hooks related to the public-facing functionality
+     * of the plugin.
+     *
+     * @since    1.0.0
+     * @access   private
+     */
+    private function define_public_hooks() {
+        $plugin_public = new QDR_TSS_Public($this->get_plugin_name(), $this->get_version());
+        $plugin_shortcode = new QDR_TSS_Shortcode($this->get_plugin_name(), $this->get_version());
+        $plugin_download = new QDR_TSS_Download($this->get_plugin_name(), $this->get_version());
+
+        // Public hooks
+        $this->loader->add_action('wp_enqueue_scripts', $plugin_public, 'enqueue_styles');
+        $this->loader->add_action('wp_enqueue_scripts', $plugin_public, 'enqueue_scripts');
+        $this->loader->add_action('init', $plugin_public, 'create_download_page');
+
+        // Shortcode registration
+        $this->loader->add_action('init', $plugin_shortcode, 'register_shortcodes');
+    }
+
+    /**
+     * Register all of the hooks related to the REST API functionality
+     * of the plugin.
+     *
+     * @since    1.0.0
+     * @access   private
+     */
+    private function define_rest_api_hooks() {
+        $rest_controller = new QDR_TSS_REST_Controller($this->get_plugin_name(), $this->get_version());
+        $media_controller = new QDR_TSS_Media_Controller($this->get_plugin_name(), $this->get_version());
+        $upload_controller = new QDR_TSS_Upload_Controller($this->get_plugin_name(), $this->get_version());
+
+        // REST API hooks
+        $this->loader->add_action('rest_api_init', $rest_controller, 'register_routes');
+        $this->loader->add_filter('rest_authentication_errors', $rest_controller, 'check_api_key');
+    }
+
+    /**
+     * Register all of the hooks related to the cron functionality
+     * of the plugin.
+     *
+     * @since    1.0.0
+     * @access   private
+     */
+    private function define_cron_hooks() {
+        $cron_manager = new QDR_TSS_Cron_Manager($this->get_plugin_name(), $this->get_version());
+
+        // Cron hooks
+        $this->loader->add_action('qdr_tss_purge_expired_files', $cron_manager, 'purge_expired_files');
+    }
+
+    /**
+     * Run the loader to execute all of the hooks with WordPress.
+     *
+     * @since    1.0.0
+     */
+    public function run() {
+        $this->loader->run();
+    }
+
+    /**
+     * The name of the plugin used to uniquely identify it within the context of
+     * WordPress and to define internationalization functionality.
+     *
+     * @since     1.0.0
+     * @return    string    The name of the plugin.
+     */
+    public function get_plugin_name() {
+        return $this->plugin_name;
+    }
+
+    /**
+     * The reference to the class that orchestrates the hooks with the plugin.
+     *
+     * @since     1.0.0
+     * @return    QDR_TSS_Loader    Orchestrates the hooks of the plugin.
+     */
+    public function get_loader() {
+        return $this->loader;
+    }
+
+    /**
+     * Retrieve the version number of the plugin.
+     *
+     * @since     1.0.0
+     * @return    string    The version number of the plugin.
+     */
+    public function get_version() {
+        return $this->version;
+    }
+}

+ 127 - 0
qdr-temporary-shared-storage/includes/functions.php

@@ -0,0 +1,127 @@
+<?php
+/**
+ * Helper functions for the plugin.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+
+if (!defined('WPINC')) {
+    die;
+}
+
+/**
+ * Format bytes to human readable format.
+ *
+ * @since 1.0.0
+ * @param int $bytes     The size in bytes.
+ * @param int $precision The number of decimal places.
+ * @return string Human readable size.
+ */
+function qdr_tss_format_bytes($bytes, $precision = 2) {
+    $units = array('B', 'KB', 'MB', 'GB', 'TB');
+    
+    $bytes = max($bytes, 0);
+    $pow = floor(($bytes ? log($bytes) : 0) / log(1024));
+    $pow = min($pow, count($units) - 1);
+    
+    $bytes /= pow(1024, $pow);
+    
+    return round($bytes, $precision) . ' ' . $units[$pow];
+}
+
+/**
+ * Format date to localized format.
+ *
+ * @since 1.0.0
+ * @param string $date_string Date string in MySQL format.
+ * @return string Formatted date.
+ */
+function qdr_tss_format_date($date_string) {
+    if (!$date_string || $date_string === '0000-00-00 00:00:00') {
+        return '-';
+    }
+    
+    $date = strtotime($date_string);
+    return date_i18n(get_option('date_format') . ' ' . get_option('time_format'), $date);
+}
+
+/**
+ * Check if a date is expired.
+ *
+ * @since 1.0.0
+ * @param string $date_string Date string in MySQL format.
+ * @return bool True if date is expired, false otherwise.
+ */
+function qdr_tss_is_expired($date_string) {
+    if (!$date_string || $date_string === '0000-00-00 00:00:00') {
+        return false;
+    }
+    
+    $expiry_date = strtotime($date_string);
+    $current_time = current_time('timestamp');
+    
+    return $current_time > $expiry_date;
+}
+
+/**
+ * Check if a date is in the future.
+ *
+ * @since 1.0.0
+ * @param string $date_string Date string in MySQL format.
+ * @return bool True if date is in the future, false otherwise.
+ */
+function qdr_tss_is_future($date_string) {
+    if (!$date_string || $date_string === '0000-00-00 00:00:00') {
+        return false;
+    }
+    
+    $future_date = strtotime($date_string);
+    $current_time = current_time('timestamp');
+    
+    return $current_time < $future_date;
+}
+
+/**
+ * Get file status class based on active dates.
+ *
+ * @since 1.0.0
+ * @param string $active_from Active from date in MySQL format.
+ * @param string $active_to   Active to date in MySQL format.
+ * @return string CSS class for status.
+ */
+function qdr_tss_get_status_class($active_from, $active_to) {
+    $current_time = current_time('timestamp');
+    $from_time = strtotime($active_from);
+    $to_time = strtotime($active_to);
+    
+    if ($current_time < $from_time) {
+        return 'qdr-tss-status-pending';
+    } elseif ($current_time > $to_time) {
+        return 'qdr-tss-status-expired';
+    } else {
+        return 'qdr-tss-status-active';
+    }
+}
+
+/**
+ * Get file status label based on active dates.
+ *
+ * @since 1.0.0
+ * @param string $active_from Active from date in MySQL format.
+ * @param string $active_to   Active to date in MySQL format.
+ * @return string Status label.
+ */
+function qdr_tss_get_status_label($active_from, $active_to) {
+    $current_time = current_time('timestamp');
+    $from_time = strtotime($active_from);
+    $to_time = strtotime($active_to);
+    
+    if ($current_time < $from_time) {
+        return __('Pending', 'qdr-temporary-shared-storage');
+    } elseif ($current_time > $to_time) {
+        return __('Expired', 'qdr-temporary-shared-storage');
+    } else {
+        return __('Active', 'qdr-temporary-shared-storage');
+    }
+}

+ 207 - 0
qdr-temporary-shared-storage/public/class-qdr-tss-download.php

@@ -0,0 +1,207 @@
+<?php
+/**
+ * Download functionality of the plugin.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+class QDR_TSS_Download {
+
+    /**
+     * The ID of this plugin.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $plugin_name    The ID of this plugin.
+     */
+    private $plugin_name;
+
+    /**
+     * The version of this plugin.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $version    The current version of this plugin.
+     */
+    private $version;
+
+    /**
+     * The database manager instance.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      QDR_TSS_DB_Manager    $db_manager    The database manager instance.
+     */
+    private $db_manager;
+
+    /**
+     * The file manager instance.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      QDR_TSS_File_Manager    $file_manager    The file manager instance.
+     */
+    private $file_manager;
+
+    /**
+     * Initialize the class and set its properties.
+     *
+     * @since    1.0.0
+     * @param    string    $plugin_name    The name of this plugin.
+     * @param    string    $version        The version of this plugin.
+     */
+    public function __construct($plugin_name, $version) {
+        $this->plugin_name = $plugin_name;
+        $this->version = $version;
+        $this->db_manager = QDR_TSS_DB_Manager::instance();
+        $this->file_manager = QDR_TSS_File_Manager::instance();
+    }
+
+    /**
+     * Process download request.
+     *
+     * @since    1.0.0
+     * @param    array    $atts    Shortcode attributes.
+     * @return   string            Download form or file download.
+     */
+    public function process_download_request($atts) {
+        // Extract attributes
+        $atts = shortcode_atts(array(
+            'id' => null,
+        ), $atts, 'qdr_tss_download');
+        
+        // Get media ID from shortcode attribute or URL parameter
+        $media_id = $atts['id'];
+        if (!$media_id && isset($_GET['id'])) {
+            $media_id = intval($_GET['id']);
+        }
+        
+        // If no media ID, show form to enter media ID, password and reference
+        if (!$media_id) {
+            return $this->render_download_form();
+        }
+        
+        // Get media information
+        $item = $this->db_manager->get_item_by_media_id($media_id);
+        
+        if (!$item) {
+            return '<div class="qdr-tss-error">' . __('File not found.', 'qdr-temporary-shared-storage') . '</div>';
+        }
+        
+        // Check if file is active
+        $current_time = current_time('mysql');
+        if ($current_time < $item->active_from) {
+            return '<div class="qdr-tss-error">' . __('This file is not yet available for download.', 'qdr-temporary-shared-storage') . '</div>';
+        }
+        
+        if ($current_time > $item->active_to) {
+            return '<div class="qdr-tss-error">' . __('This file has expired and is no longer available for download.', 'qdr-temporary-shared-storage') . '</div>';
+        }
+        
+        // Check if form is submitted
+        if (isset($_POST['qdr_tss_download_submit'])) {
+            // Verify nonce
+            if (!isset($_POST['qdr_tss_nonce']) || !wp_verify_nonce($_POST['qdr_tss_nonce'], 'qdr_tss_download')) {
+                return '<div class="qdr-tss-error">' . __('Security check failed.', 'qdr-temporary-shared-storage') . '</div>';
+            }
+            
+            // Verify password
+            $password = isset($_POST['qdr_tss_password']) ? $_POST['qdr_tss_password'] : '';
+            if (!wp_check_password($password, $item->password)) {
+                return '<div class="qdr-tss-error">' . __('Invalid password.', 'qdr-temporary-shared-storage') . '</div>' . $this->render_download_form($media_id);
+            }
+            
+            // Verify reference if set
+            if (!empty($item->reference)) {
+                $reference = isset($_POST['qdr_tss_reference']) ? $_POST['qdr_tss_reference'] : '';
+                if ($reference !== $item->reference) {
+                    return '<div class="qdr-tss-error">' . __('Invalid reference.', 'qdr-temporary-shared-storage') . '</div>' . $this->render_download_form($media_id);
+                }
+            }
+            
+            // Update download count and last download time
+            $this->db_manager->increment_download_count($item->id);
+            
+            // Initiate download
+            $file_path = QDR_TSS_UPLOADS_DIR . $item->file_path;
+            
+            if (file_exists($file_path)) {
+                // Set headers for download
+                header('Content-Description: File Transfer');
+                header('Content-Type: application/octet-stream');
+                header('Content-Disposition: attachment; filename="' . $item->original_file_name . '"');
+                header('Expires: 0');
+                header('Cache-Control: must-revalidate');
+                header('Pragma: public');
+                header('Content-Length: ' . filesize($file_path));
+                ob_clean();
+                flush();
+                readfile($file_path);
+                exit;
+            } else {
+                return '<div class="qdr-tss-error">' . __('File not found on server.', 'qdr-temporary-shared-storage') . '</div>';
+            }
+        }
+        
+        // Show download form with media information
+        $output = '<div class="qdr-tss-download-info">';
+        $output .= '<h2>' . __('File Information', 'qdr-temporary-shared-storage') . '</h2>';
+        $output .= '<p><strong>' . __('File Name:', 'qdr-temporary-shared-storage') . '</strong> ' . esc_html($item->original_file_name) . '</p>';
+        
+        if (!empty($item->description)) {
+            $output .= '<p><strong>' . __('Description:', 'qdr-temporary-shared-storage') . '</strong> ' . esc_html($item->description) . '</p>';
+        }
+        
+        if (!empty($item->message)) {
+            $output .= '<p><strong>' . __('Message:', 'qdr-temporary-shared-storage') . '</strong> ' . esc_html($item->message) . '</p>';
+        }
+        
+        $output .= '</div>';
+        $output .= $this->render_download_form($media_id);
+        
+        return $output;
+    }
+
+    /**
+     * Render download form.
+     *
+     * @since    1.0.0
+     * @param    int       $media_id    Optional. Media ID.
+     * @return   string                 Download form HTML.
+     */
+    private function render_download_form($media_id = null) {
+        $form = '<div class="qdr-tss-download-form">';
+        $form .= '<form method="post" action="">';
+        
+        // Add nonce for security
+        $form .= wp_nonce_field('qdr_tss_download', 'qdr_tss_nonce', true, false);
+        
+        if (!$media_id) {
+            $form .= '<div class="qdr-tss-form-group">';
+            $form .= '<label for="qdr_tss_media_id">' . __('Media ID', 'qdr-temporary-shared-storage') . '</label>';
+            $form .= '<input type="text" name="id" id="qdr_tss_media_id" required>';
+            $form .= '</div>';
+        } else {
+            $form .= '<input type="hidden" name="id" value="' . esc_attr($media_id) . '">';
+        }
+        
+        $form .= '<div class="qdr-tss-form-group">';
+        $form .= '<label for="qdr_tss_password">' . __('Password', 'qdr-temporary-shared-storage') . '</label>';
+        $form .= '<input type="password" name="qdr_tss_password" id="qdr_tss_password" required>';
+        $form .= '</div>';
+        
+        $form .= '<div class="qdr-tss-form-group">';
+        $form .= '<label for="qdr_tss_reference">' . __('Reference (if required)', 'qdr-temporary-shared-storage') . '</label>';
+        $form .= '<input type="text" name="qdr_tss_reference" id="qdr_tss_reference">';
+        $form .= '</div>';
+        
+        $form .= '<div class="qdr-tss-form-group">';
+        $form .= '<button type="submit" name="qdr_tss_download_submit" class="qdr-tss-button">' . __('Download File', 'qdr-temporary-shared-storage') . '</button>';
+        $form .= '</div>';
+        
+        $form .= '</form>';
+        $form .= '</div>';
+        
+        return $form;
+    }
+}

+ 97 - 0
qdr-temporary-shared-storage/public/class-qdr-tss-public.php

@@ -0,0 +1,97 @@
+<?php
+/**
+ * The public-facing functionality of the plugin.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+class QDR_TSS_Public {
+
+    /**
+     * The ID of this plugin.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $plugin_name    The ID of this plugin.
+     */
+    private $plugin_name;
+
+    /**
+     * The version of this plugin.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $version    The current version of this plugin.
+     */
+    private $version;
+
+    /**
+     * Initialize the class and set its properties.
+     *
+     * @since    1.0.0
+     * @param    string    $plugin_name    The name of this plugin.
+     * @param    string    $version        The version of this plugin.
+     */
+    public function __construct($plugin_name, $version) {
+        $this->plugin_name = $plugin_name;
+        $this->version = $version;
+    }
+
+    /**
+     * Register the stylesheets for the public-facing side of the site.
+     *
+     * @since    1.0.0
+     */
+    public function enqueue_styles() {
+        // Only load on download page
+        if (!is_page('shared-file-download')) {
+            return;
+        }
+        
+        wp_enqueue_style(
+            $this->plugin_name . '-public',
+            plugin_dir_url(dirname(__FILE__)) . 'public/css/public.css',
+            array(),
+            $this->version,
+            'all'
+        );
+    }
+
+    /**
+     * Register the JavaScript for the public-facing side of the site.
+     *
+     * @since    1.0.0
+     */
+    public function enqueue_scripts() {
+        // Only load on download page
+        if (!is_page('shared-file-download')) {
+            return;
+        }
+        
+        wp_enqueue_script(
+            $this->plugin_name . '-public',
+            plugin_dir_url(dirname(__FILE__)) . 'public/js/public.js',
+            array('jquery'),
+            $this->version,
+            true
+        );
+    }
+
+    /**
+     * Create download page if it doesn't exist.
+     *
+     * @since    1.0.0
+     */
+    public function create_download_page() {
+        $download_page = get_page_by_path('shared-file-download');
+        if (!$download_page) {
+            wp_insert_post(array(
+                'post_title' => __('Shared File Download', 'qdr-temporary-shared-storage'),
+                'post_content' => '[qdr_tss_download]',
+                'post_status' => 'publish',
+                'post_type' => 'page',
+                'post_name' => 'shared-file-download'
+            ));
+        }
+    }
+}

+ 69 - 0
qdr-temporary-shared-storage/public/class-qdr-tss-shortcode.php

@@ -0,0 +1,69 @@
+<?php
+/**
+ * Shortcode functionality of the plugin.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+class QDR_TSS_Shortcode {
+
+    /**
+     * The ID of this plugin.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $plugin_name    The ID of this plugin.
+     */
+    private $plugin_name;
+
+    /**
+     * The version of this plugin.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $version    The current version of this plugin.
+     */
+    private $version;
+
+    /**
+     * The download handler instance.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      QDR_TSS_Download    $download    The download handler instance.
+     */
+    private $download;
+
+    /**
+     * Initialize the class and set its properties.
+     *
+     * @since    1.0.0
+     * @param    string    $plugin_name    The name of this plugin.
+     * @param    string    $version        The version of this plugin.
+     */
+    public function __construct($plugin_name, $version) {
+        $this->plugin_name = $plugin_name;
+        $this->version = $version;
+        $this->download = new QDR_TSS_Download($plugin_name, $version);
+    }
+
+    /**
+     * Register shortcodes.
+     *
+     * @since    1.0.0
+     */
+    public function register_shortcodes() {
+        add_shortcode('qdr_tss_download', array($this, 'download_shortcode'));
+    }
+
+    /**
+     * Download shortcode.
+     *
+     * @since    1.0.0
+     * @param    array    $atts    Shortcode attributes.
+     * @return   string            Shortcode output.
+     */
+    public function download_shortcode($atts) {
+        return $this->download->process_download_request($atts);
+    }
+}

+ 61 - 0
qdr-temporary-shared-storage/public/css/public.css

@@ -0,0 +1,61 @@
+/**
+ * Public styles for the QDR Temporary Shared Storage plugin
+ */
+
+/* Download page */
+.qdr-tss-download-form {
+    max-width: 600px;
+    margin: 20px auto;
+    padding: 20px;
+    background-color: #f8f8f8;
+    border-radius: 5px;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.qdr-tss-download-info {
+    max-width: 600px;
+    margin: 20px auto;
+    padding: 20px;
+    background-color: #fff;
+    border-radius: 5px;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+
+.qdr-tss-error {
+    color: #d63638;
+    margin: 20px 0;
+    padding: 10px;
+    background-color: #ffdddd;
+    border-left: 4px solid #d63638;
+}
+
+.qdr-tss-form-group {
+    margin-bottom: 15px;
+}
+
+.qdr-tss-form-group label {
+    display: block;
+    margin-bottom: 5px;
+    font-weight: bold;
+}
+
+.qdr-tss-form-group input {
+    width: 100%;
+    padding: 8px;
+    border: 1px solid #ddd;
+    border-radius: 3px;
+}
+
+.qdr-tss-button {
+    background-color: #2271b1;
+    color: #fff;
+    border: none;
+    padding: 10px 15px;
+    border-radius: 3px;
+    cursor: pointer;
+    font-size: 14px;
+}
+
+.qdr-tss-button:hover {
+    background-color: #135e96;
+}

+ 14 - 0
qdr-temporary-shared-storage/public/js/public.js

@@ -0,0 +1,14 @@
+/**
+ * Public JavaScript for the QDR Temporary Shared Storage plugin
+ */
+
+(function($) {
+    'use strict';
+
+    // Document ready
+    $(function() {
+        // Add focus to first input
+        $('.qdr-tss-download-form input:first').focus();
+    });
+
+})(jQuery);

+ 28 - 1200
qdr-temporary-shared-storage/qdr-temporary-shared-storage.php

@@ -21,1215 +21,43 @@ if (!defined('WPINC')) {
     die;
     die;
 }
 }
 
 
-// Define plugin constants
+// Define plugin version
 define('QDR_TSS_VERSION', '1.0.0');
 define('QDR_TSS_VERSION', '1.0.0');
-define('QDR_TSS_PLUGIN_DIR', plugin_dir_path(__FILE__));
-define('QDR_TSS_PLUGIN_URL', plugin_dir_url(__FILE__));
-define('QDR_TSS_PLUGIN_BASENAME', plugin_basename(__FILE__));
-define('QDR_TSS_UPLOADS_DIR', wp_upload_dir()['basedir'] . '/qdr-shared-storage/');
-define('QDR_TSS_UPLOADS_URL', wp_upload_dir()['baseurl'] . '/qdr-shared-storage/');
-define('QDR_TSS_PLUGIN_TABLE', 'qdr_temporary_shared_storage');
 
 
 /**
 /**
- * The core plugin class
+ * The code that runs during plugin activation.
  */
  */
-class QDR_Temporary_Shared_Storage {
-
-    /**
-     * Plugin instance.
-     *
-     * @var QDR_Temporary_Shared_Storage
-     */
-    private static $instance = null;
-
-    /**
-     * Plugin settings.
-     *
-     * @var array
-     */
-    private $settings;
-
-    /**
-     * Get plugin instance.
-     *
-     * @return QDR_Temporary_Shared_Storage
-     */
-    public static function get_instance() {
-        if (null === self::$instance) {
-            self::$instance = new self();
-        }
-        return self::$instance;
-    }
-
-    /**
-     * Constructor.
-     */
-    private function __construct() {
-        // Load plugin textdomain for translations
-        add_action('plugins_loaded', array($this, 'load_textdomain'));
-        
-        // Register activation and deactivation hooks
-        register_activation_hook(__FILE__, array($this, 'activate'));
-        register_deactivation_hook(__FILE__, array($this, 'deactivate'));
-        
-        // Initialize the plugin
-        add_action('init', array($this, 'init'));
-        
-        // Add admin menu
-        add_action('admin_menu', array($this, 'add_admin_menu'));
-        
-        // Register REST API routes
-        add_action('rest_api_init', array($this, 'register_rest_routes'));
-        
-        // Register shortcode
-        add_shortcode('qdr_tss_download', array($this, 'download_shortcode'));
-        
-        // Schedule cron job
-        if (!wp_next_scheduled('qdr_tss_purge_expired_files')) {
-            wp_schedule_event(time(), 'hourly', 'qdr_tss_purge_expired_files');
-        }
-        add_action('qdr_tss_purge_expired_files', array($this, 'purge_expired_files'));
-        
-        // Load settings
-        $this->settings = get_option('qdr_tss_settings', array(
-            'api_key' => wp_generate_password(32, false),
-            'media_category' => 'shared-storage',
-            'max_upload_size' => 1073741824, // 1GB default
-        ));
-        
-        // Add REST API check
-        add_filter('rest_authentication_errors', array($this, 'check_api_key'));
-    }
-
-    /**
-     * Load plugin textdomain.
-     */
-    public function load_textdomain() {
-        load_plugin_textdomain('qdr-temporary-shared-storage', false, dirname(plugin_basename(__FILE__)) . '/languages');
-    }
-
-    /**
-     * Plugin activation.
-     */
-    public function activate() {
-        global $wpdb;
-        
-        // Create uploads directory if it doesn't exist
-        if (!file_exists(QDR_TSS_UPLOADS_DIR)) {
-            wp_mkdir_p(QDR_TSS_UPLOADS_DIR);
-            
-            // Create .htaccess file to protect direct access
-            $htaccess_content = "# Disable directory browsing\nOptions -Indexes\n\n# Deny direct access to all files\n<FilesMatch \".*\">\nOrder Allow,Deny\nDeny from all\n</FilesMatch>";
-            file_put_contents(QDR_TSS_UPLOADS_DIR . '.htaccess', $htaccess_content);
-        }
-        
-        // Create custom table
-        $table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE;
-        $charset_collate = $wpdb->get_charset_collate();
-        
-        $sql = "CREATE TABLE $table_name (
-            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)
-        ) $charset_collate;";
-        
-        require_once(ABSPATH . 'wp-admin/includes/upgrade.php');
-        dbDelta($sql);
-        
-        // Default settings
-        if (!get_option('qdr_tss_settings')) {
-            add_option('qdr_tss_settings', array(
-                'api_key' => wp_generate_password(32, false),
-                'media_category' => 'shared-storage',
-                'max_upload_size' => 1073741824, // 1GB default
-            ));
-        }
-    }
-
-    /**
-     * Plugin deactivation.
-     */
-    public function deactivate() {
-        // Clear scheduled cron job
-        wp_clear_scheduled_hook('qdr_tss_purge_expired_files');
-    }
-
-    /**
-     * Initialize plugin.
-     */
-    public function init() {
-        // Register scripts and styles
-        add_action('admin_enqueue_scripts', array($this, 'enqueue_admin_scripts'));
-        
-        // Create download page if it doesn't exist
-        $download_page = get_page_by_path('shared-file-download');
-        if (!$download_page) {
-            $page_id = wp_insert_post(array(
-                'post_title' => __('Shared File Download', 'qdr-temporary-shared-storage'),
-                'post_content' => '[qdr_tss_download]',
-                'post_status' => 'publish',
-                'post_type' => 'page',
-                'post_name' => 'shared-file-download'
-            ));
-        }
-    }
-
-    /**
-     * Enqueue admin scripts and styles.
-     */
-    public function enqueue_admin_scripts($hook) {
-        // Only load on plugin admin pages
-        if (strpos($hook, 'qdr-temporary-shared-storage') === false) {
-            return;
-        }
-        
-        wp_enqueue_style('qdr-tss-admin-css', QDR_TSS_PLUGIN_URL . 'assets/css/admin.css', array(), QDR_TSS_VERSION);
-        wp_enqueue_script('qdr-tss-admin-js', QDR_TSS_PLUGIN_URL . 'assets/js/admin.js', array('jquery', 'jquery-ui-datepicker'), QDR_TSS_VERSION, true);
-        
-        // Add datepicker
-        wp_enqueue_style('jquery-ui-datepicker');
-        
-        // Localize script
-        wp_localize_script('qdr-tss-admin-js', 'qdr_tss', array(
-            'ajaxurl' => admin_url('admin-ajax.php'),
-            'nonce' => wp_create_nonce('qdr_tss_nonce'),
-            'strings' => array(
-                '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')
-            )
-        ));
-    }
-
-    /**
-     * Add admin menu items.
-     */
-    public function add_admin_menu() {
-        // Add Shared Storage page under Media menu
-        add_submenu_page(
-            'upload.php',
-            __('Shared Storage', 'qdr-temporary-shared-storage'),
-            __('Shared Storage', 'qdr-temporary-shared-storage'),
-            'manage_options',
-            'qdr-temporary-shared-storage',
-            array($this, 'render_media_page')
-        );
-        
-        // Add Settings page
-        add_submenu_page(
-            'options-general.php',
-            __('Shared Storage Settings', 'qdr-temporary-shared-storage'),
-            __('Shared Storage', 'qdr-temporary-shared-storage'),
-            'manage_options',
-            'qdr-temporary-shared-storage-settings',
-            array($this, 'render_settings_page')
-        );
-    }
-
-    /**
-     * Render the Media Shared Storage admin page.
-     */
-    public function render_media_page() {
-        include_once QDR_TSS_PLUGIN_DIR . 'admin/media-page.php';
-    }
-
-    /**
-     * Render the Settings admin page.
-     */
-    public function render_settings_page() {
-        include_once QDR_TSS_PLUGIN_DIR . 'admin/settings-page.php';
-    }
-
-    /**
-     * Register REST API routes.
-     */
-    public function register_rest_routes() {
-        register_rest_route('qdr-tss/v1', '/media', array(
-            'methods' => 'POST',
-            'callback' => array($this, 'upload_media'),
-            'permission_callback' => array($this, 'check_rest_permission')
-        ));
-        
-        register_rest_route('qdr-tss/v1', '/media/(?P<id>\d+)', array(
-            'methods' => 'GET',
-            'callback' => array($this, 'get_media'),
-            'permission_callback' => array($this, 'check_rest_permission')
-        ));
-        
-        register_rest_route('qdr-tss/v1', '/media/(?P<id>\d+)', array(
-            'methods' => 'PUT',
-            'callback' => array($this, 'update_media'),
-            'permission_callback' => array($this, 'check_rest_permission')
-        ));
-        
-        register_rest_route('qdr-tss/v1', '/media/(?P<id>\d+)', array(
-            'methods' => 'DELETE',
-            'callback' => array($this, 'delete_media'),
-            'permission_callback' => array($this, 'check_rest_permission')
-        ));
-        
-        register_rest_route('qdr-tss/v1', '/media', array(
-            'methods' => 'GET',
-            'callback' => array($this, 'get_media_collection'),
-            'permission_callback' => array($this, 'check_rest_permission')
-        ));
-        
-        // Chunked upload endpoints
-        register_rest_route('qdr-tss/v1', '/upload/init', array(
-            'methods' => 'POST',
-            'callback' => array($this, 'init_chunked_upload'),
-            'permission_callback' => array($this, 'check_rest_permission')
-        ));
-        
-        register_rest_route('qdr-tss/v1', '/upload/chunk', array(
-            'methods' => 'POST',
-            'callback' => array($this, 'upload_chunk'),
-            'permission_callback' => array($this, 'check_rest_permission')
-        ));
-        
-        register_rest_route('qdr-tss/v1', '/upload/finalize', array(
-            'methods' => 'POST',
-            'callback' => array($this, 'finalize_chunked_upload'),
-            'permission_callback' => array($this, 'check_rest_permission')
-        ));
-    }
-
-    /**
-     * Check API key for REST API requests.
-     */
-    public function check_api_key($result) {
-        // Only check for our namespace
-        if (empty($result) && strpos($_SERVER['REQUEST_URI'], '/wp-json/qdr-tss/') !== false) {
-            $headers = getallheaders();
-            $api_key = isset($headers['X-Api-Key']) ? $headers['X-Api-Key'] : '';
-            
-            if ($api_key !== $this->settings['api_key']) {
-                return new WP_Error(
-                    'rest_forbidden',
-                    __('Invalid API key.', 'qdr-temporary-shared-storage'),
-                    array('status' => 403)
-                );
-            }
-        }
-        
-        return $result;
-    }
-
-    /**
-     * Check REST API permissions.
-     */
-    public function check_rest_permission() {
-        $headers = getallheaders();
-        $api_key = isset($headers['X-Api-Key']) ? $headers['X-Api-Key'] : '';
-        
-        return ($api_key === $this->settings['api_key']);
-    }
-
-    /**
-     * Initialize chunked upload.
-     */
-    public function init_chunked_upload($request) {
-        $params = $request->get_params();
-        
-        // Check required parameters
-        if (!isset($params['original_file_name']) || !isset($params['file_size'])) {
-            return new WP_Error('missing_params', __('Missing required parameters.', 'qdr-temporary-shared-storage'), array('status' => 400));
-        }
-        
-        // Check max upload size
-        if (intval($params['file_size']) > $this->settings['max_upload_size']) {
-            return new WP_Error('file_too_large', __('File exceeds maximum upload size.', 'qdr-temporary-shared-storage'), array('status' => 400));
-        }
-        
-        // Check total storage usage
-        if (!$this->check_storage_limit(intval($params['file_size']))) {
-            return new WP_Error('storage_limit', __('Storage limit exceeded.', 'qdr-temporary-shared-storage'), array('status' => 400));
-        }
-        
-        // Create temporary upload directory
-        $upload_id = uniqid();
-        $upload_dir = QDR_TSS_UPLOADS_DIR . 'tmp/' . $upload_id;
-        
-        if (!file_exists(QDR_TSS_UPLOADS_DIR . 'tmp/')) {
-            wp_mkdir_p(QDR_TSS_UPLOADS_DIR . 'tmp/');
-        }
-        
-        wp_mkdir_p($upload_dir);
-        
-        return array(
-            'upload_id' => $upload_id,
-            'status' => 'initialized'
-        );
-    }
-
-    /**
-     * Upload a chunk.
-     */
-    public function upload_chunk($request) {
-        $upload_id = $request->get_param('upload_id');
-        $chunk_index = $request->get_param('chunk_index');
-        $total_chunks = $request->get_param('total_chunks');
-        
-        if (!$upload_id || !isset($chunk_index) || !isset($total_chunks)) {
-            return new WP_Error('missing_params', __('Missing required parameters.', 'qdr-temporary-shared-storage'), array('status' => 400));
-        }
-        
-        $upload_dir = QDR_TSS_UPLOADS_DIR . 'tmp/' . $upload_id;
-        
-        // Check if the upload directory exists
-        if (!file_exists($upload_dir)) {
-            return new WP_Error('invalid_upload_id', __('Invalid upload ID.', 'qdr-temporary-shared-storage'), array('status' => 400));
-        }
-        
-        // Handle file upload
-        $files = $request->get_file_params();
-        
-        if (empty($files) || !isset($files['chunk'])) {
-            return new WP_Error('no_file', __('No file uploaded.', 'qdr-temporary-shared-storage'), array('status' => 400));
-        }
-        
-        $chunk_file = $files['chunk'];
-        
-        // Move uploaded chunk to temporary directory
-        $chunk_path = $upload_dir . '/' . $chunk_index;
-        move_uploaded_file($chunk_file['tmp_name'], $chunk_path);
-        
-        return array(
-            'upload_id' => $upload_id,
-            'chunk_index' => $chunk_index,
-            'status' => 'chunk_uploaded'
-        );
-    }
-
-    /**
-     * Finalize chunked upload.
-     */
-    public function finalize_chunked_upload($request) {
-        global $wpdb;
-        
-        $params = $request->get_params();
-        $upload_id = $params['upload_id'];
-        $total_chunks = $params['total_chunks'];
-        
-        if (!$upload_id || !isset($total_chunks)) {
-            return new WP_Error('missing_params', __('Missing required parameters.', 'qdr-temporary-shared-storage'), array('status' => 400));
-        }
-        
-        // Check for required file metadata
-        $required_fields = array('original_file_name', 'description', 'password');
-        foreach ($required_fields as $field) {
-            if (!isset($params[$field]) || empty($params[$field])) {
-                return new WP_Error('missing_field', sprintf(__('Missing required field: %s', 'qdr-temporary-shared-storage'), $field), array('status' => 400));
-            }
-        }
-        
-        $upload_dir = QDR_TSS_UPLOADS_DIR . 'tmp/' . $upload_id;
-        
-        // Check that all chunks are present
-        for ($i = 0; $i < $total_chunks; $i++) {
-            if (!file_exists($upload_dir . '/' . $i)) {
-                return new WP_Error('missing_chunks', __('Not all chunks have been uploaded.', 'qdr-temporary-shared-storage'), array('status' => 400));
-            }
-        }
-        
-        // Create final file directory
-        $file_dir = QDR_TSS_UPLOADS_DIR . date('Y/m/d');
-        if (!file_exists($file_dir)) {
-            wp_mkdir_p($file_dir);
-        }
-        
-        // Sanitize file name
-        $original_file_name = sanitize_file_name($params['original_file_name']);
-        $file_name = md5($original_file_name . time()) . '-' . $original_file_name;
-        $file_path = $file_dir . '/' . $file_name;
-        
-        // Combine chunks
-        $final_file = fopen($file_path, 'wb');
-        
-        for ($i = 0; $i < $total_chunks; $i++) {
-            $chunk_path = $upload_dir . '/' . $i;
-            $chunk = fopen($chunk_path, 'rb');
-            stream_copy_to_stream($chunk, $final_file);
-            fclose($chunk);
-            unlink($chunk_path);
-        }
-        
-        fclose($final_file);
-        
-        // Clean up temporary directory
-        rmdir($upload_dir);
-        
-        // Get file size
-        $file_size = filesize($file_path);
-        
-        // Set dates
-        $active_from = !empty($params['active_from']) ? $params['active_from'] : current_time('mysql');
-        $active_to = !empty($params['active_to']) ? $params['active_to'] : date('Y-m-d H:i:s', strtotime('+30 days'));
-        
-        // Generate a unique media ID and insert into database
-        $media_id = time() . rand(1000, 9999);
-        
-        $table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE;
-        
-        $wpdb->insert(
-            $table_name,
-            array(
-                'media_id' => $media_id,
-                'original_file_name' => $original_file_name,
-                'description' => sanitize_textarea_field($params['description']),
-                'message' => isset($params['message']) ? sanitize_textarea_field($params['message']) : '',
-                'active_from' => $active_from,
-                'active_to' => $active_to,
-                'password' => wp_hash_password($params['password']),
-                'reference' => isset($params['reference']) ? sanitize_text_field($params['reference']) : '',
-                'upload_date' => current_time('mysql'),
-                'file_path' => str_replace(QDR_TSS_UPLOADS_DIR, '', $file_path),
-                'file_size' => $file_size
-            ),
-            array('%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%d')
-        );
-        
-        $download_url = site_url('shared-file-download/' . $media_id);
-        $shortcode = '[qdr_tss_download id="' . $media_id . '"]';
-        
-        return array(
-            'media_id' => $media_id,
-            'permalink' => $download_url,
-            'shortcode' => $shortcode
-        );
-    }
-
-    /**
-     * Get single media details.
-     */
-    public function get_media($request) {
-        global $wpdb;
-        
-        $id = $request->get_param('id');
-        $table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE;
-        
-        $item = $wpdb->get_row($wpdb->prepare(
-            "SELECT media_id, original_file_name, description, message, active_from, active_to, reference, upload_date, download_count, last_download, file_size
-             FROM $table_name
-             WHERE media_id = %d",
-            $id
-        ));
-        
-        if (!$item) {
-            return new WP_Error('not_found', __('Media not found.', 'qdr-temporary-shared-storage'), array('status' => 404));
-        }
-        
-        // Get additional information from WordPress attachment
-        $attachment = get_post($id);
-        if ($attachment && $attachment->post_type === 'attachment') {
-            $item->attachment_url = wp_get_attachment_url($id);
-            $item->attachment_title = $attachment->post_title;
-        }
-        
-        return $item;
-    }
-
-    /**
-     * Update media details.
-     */
-    public function update_media($request) {
-        global $wpdb;
-        
-        $id = $request->get_param('id');
-        $params = $request->get_params();
-        
-        $table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE;
-        
-        // Check if media exists in our custom table
-        $item = $wpdb->get_row($wpdb->prepare(
-            "SELECT id FROM $table_name WHERE media_id = %d",
-            $id
-        ));
-        
-        if (!$item) {
-            return new WP_Error('not_found', __('Media not found.', 'qdr-temporary-shared-storage'), array('status' => 404));
-        }
-        
-        // Check if the WordPress attachment exists
-        $attachment = get_post($id);
-        if (!$attachment || $attachment->post_type !== 'attachment') {
-            return new WP_Error('attachment_not_found', __('WordPress attachment not found.', 'qdr-temporary-shared-storage'), array('status' => 404));
-        }
-        
-        // Prepare update data for our custom table
-        $update_data = array();
-        $update_format = array();
-        
-        if (isset($params['message'])) {
-            $update_data['message'] = sanitize_textarea_field($params['message']);
-            $update_format[] = '%s';
-        }
-        
-        if (isset($params['active_from'])) {
-            $update_data['active_from'] = $params['active_from'];
-            $update_format[] = '%s';
-        }
-        
-        if (isset($params['active_to'])) {
-            $update_data['active_to'] = $params['active_to'];
-            $update_format[] = '%s';
-        }
-        
-        if (isset($params['reference'])) {
-            $update_data['reference'] = sanitize_text_field($params['reference']);
-            $update_format[] = '%s';
-        }
-        
-        if (empty($update_data)) {
-            return new WP_Error('no_changes', __('No changes to update.', 'qdr-temporary-shared-storage'), array('status' => 400));
-        }
-        
-        // Update in our custom table
-        $wpdb->update(
-            $table_name,
-            $update_data,
-            array('media_id' => $id),
-            $update_format,
-            array('%d')
-        );
-        
-        // Update WordPress attachment if description is provided
-        if (isset($params['description'])) {
-            $description = sanitize_textarea_field($params['description']);
-            $attachment_data = array(
-                'ID' => $id,
-                'post_excerpt' => $description
-            );
-            wp_update_post($attachment_data);
-            
-            // Also update our custom table
-            $wpdb->update(
-                $table_name,
-                array('description' => $description),
-                array('media_id' => $id),
-                array('%s'),
-                array('%d')
-            );
-        }
-        
-        // Get updated record
-        $updated_item = $wpdb->get_row($wpdb->prepare(
-            "SELECT media_id, original_file_name, description, message, active_from, active_to, reference, upload_date, download_count, last_download
-             FROM $table_name
-             WHERE media_id = %d",
-            $id
-        ));
-        
-        return $updated_item;
-    }
-
-    /**
-     * Delete media.
-     */
-    public function delete_media($request) {
-        global $wpdb;
-        
-        $id = $request->get_param('id');
-        $table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE;
-        
-        // Get file path
-        $item = $wpdb->get_row($wpdb->prepare(
-            "SELECT id, file_path FROM $table_name WHERE media_id = %d",
-            $id
-        ));
-        
-        if (!$item) {
-            return new WP_Error('not_found', __('Media not found.', 'qdr-temporary-shared-storage'), array('status' => 404));
-        }
-        
-        // Delete file
-        $file_path = QDR_TSS_UPLOADS_DIR . $item->file_path;
-        if (file_exists($file_path)) {
-            unlink($file_path);
-        }
-        
-        // Delete from database
-        $wpdb->delete($table_name, array('media_id' => $id), array('%d'));
-        
-        return array(
-            'deleted' => true,
-            'media_id' => $id
-        );
-    }
-
-    /**
-     * Get media collection.
-     */
-    public function get_media_collection($request) {
-        global $wpdb;
-        
-        $table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE;
-        
-        // Parse query parameters
-        $params = $request->get_params();
-        $per_page = isset($params['per_page']) ? intval($params['per_page']) : 10;
-        $page = isset($params['page']) ? intval($params['page']) : 1;
-        $offset = ($page - 1) * $per_page;
-        
-        // Build query
-        $query = "SELECT media_id, original_file_name, description, message, active_from, active_to, reference, upload_date, download_count, last_download, file_size
-                 FROM $table_name";
-        
-        $count_query = "SELECT COUNT(*) FROM $table_name";
-        
-        $where_conditions = array();
-        $query_values = array();
-        
-        // Filter by original_file_name
-        if (isset($params['original_file_name'])) {
-            $where_conditions[] = "original_file_name LIKE %s";
-            $query_values[] = '%' . $wpdb->esc_like($params['original_file_name']) . '%';
-        }
-        
-        // Filter by reference
-        if (isset($params['reference'])) {
-            $where_conditions[] = "reference LIKE %s";
-            $query_values[] = '%' . $wpdb->esc_like($params['reference']) . '%';
-        }
-        
-        // Add WHERE conditions if any
-        if (!empty($where_conditions)) {
-            $query .= " WHERE " . implode(" AND ", $where_conditions);
-            $count_query .= " WHERE " . implode(" AND ", $where_conditions);
-        }
-        
-        // Add sorting
-        if (isset($params['orderby'])) {
-            $allowed_orderby_columns = array(
-                'media_id', 'original_file_name', 'description', 'active_from', 'active_to',
-                'reference', 'upload_date', 'download_count', 'last_download'
-            );
-            
-            $orderby = in_array($params['orderby'], $allowed_orderby_columns) ? $params['orderby'] : 'upload_date';
-            $order = (isset($params['order']) && strtoupper($params['order']) === 'ASC') ? 'ASC' : 'DESC';
-            
-            $query .= " ORDER BY $orderby $order";
-        } else {
-            $query .= " ORDER BY upload_date DESC";
-        }
-        
-        // Add pagination
-        $query .= " LIMIT %d OFFSET %d";
-        $query_values[] = $per_page;
-        $query_values[] = $offset;
-        
-        // Prepare and execute queries
-        $items = $wpdb->get_results($wpdb->prepare($query, $query_values));
-        $total_items = $wpdb->get_var($wpdb->prepare($count_query, array_slice($query_values, 0, -2)));
-        
-        // Prepare response
-        $response = new WP_REST_Response($items);
-        $response->header('X-WP-Total', $total_items);
-        $response->header('X-WP-TotalPages', ceil($total_items / $per_page));
-        
-        return $response;
-    }
-
-    /**
-     * Check storage limit.
-     */
-    private function check_storage_limit($new_file_size) {
-        global $wpdb;
-        
-        $table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE;
-        
-        // Get total storage usage
-        $total_size = $wpdb->get_var("SELECT SUM(file_size) FROM $table_name");
-        $total_size = $total_size ? intval($total_size) : 0;
-        
-        // Check if adding new file would exceed limit
-        return ($total_size + $new_file_size) <= $this->settings['max_upload_size'];
-    }
-
-    /**
-     * Purge expired files.
-     */
-    public function purge_expired_files() {
-        global $wpdb;
-        
-        $table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE;
-        $current_time = current_time('mysql');
-        
-        // Get expired items
-        $expired_items = $wpdb->get_results($wpdb->prepare(
-            "SELECT id, media_id, file_path FROM $table_name WHERE active_to < %s",
-            $current_time
-        ));
-        
-        // Delete files and database records
-        foreach ($expired_items as $item) {
-            // Delete the WordPress attachment
-            wp_delete_attachment($item->media_id, true);
-            
-            // Delete file if it still exists (in case the WordPress function didn't remove it)
-            $file_path = QDR_TSS_UPLOADS_DIR . $item->file_path;
-            if (file_exists($file_path)) {
-                unlink($file_path);
-            }
-            
-            // Delete from our custom table
-            $wpdb->delete($table_name, array('id' => $item->id), array('%d'));
-        }
-        
-        return count($expired_items);
-    }
-
-    /**
-     * Download shortcode.
-     */
-    public function download_shortcode($atts) {
-        global $wpdb;
-        
-        // Extract attributes
-        $atts = shortcode_atts(array(
-            'id' => null,
-        ), $atts, 'qdr_tss_download');
-        
-        // Get media ID from shortcode attribute or URL parameter
-        $media_id = $atts['id'];
-        if (!$media_id && isset($_GET['id'])) {
-            $media_id = intval($_GET['id']);
-        }
-        
-        // If no media ID, show form to enter media ID, password and reference
-        if (!$media_id) {
-            return $this->render_download_form();
-        }
-        
-        $table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE;
-        
-        // Get media information
-        $item = $wpdb->get_row($wpdb->prepare(
-            "SELECT * FROM $table_name WHERE media_id = %d",
-            $media_id
-        ));
-        
-        if (!$item) {
-            return '<div class="qdr-tss-error">' . __('File not found.', 'qdr-temporary-shared-storage') . '</div>';
-        }
-        
-        // Check if file is active
-        $current_time = current_time('mysql');
-        if ($current_time < $item->active_from) {
-            return '<div class="qdr-tss-error">' . __('This file is not yet available for download.', 'qdr-temporary-shared-storage') . '</div>';
-        }
-        
-        if ($current_time > $item->active_to) {
-            return '<div class="qdr-tss-error">' . __('This file has expired and is no longer available for download.', 'qdr-temporary-shared-storage') . '</div>';
-        }
-        
-        // Check if form is submitted
-        if (isset($_POST['qdr_tss_download_submit'])) {
-            // Verify nonce
-            if (!isset($_POST['qdr_tss_nonce']) || !wp_verify_nonce($_POST['qdr_tss_nonce'], 'qdr_tss_download')) {
-                return '<div class="qdr-tss-error">' . __('Security check failed.', 'qdr-temporary-shared-storage') . '</div>';
-            }
-            
-            // Verify password
-            $password = isset($_POST['qdr_tss_password']) ? $_POST['qdr_tss_password'] : '';
-            if (!wp_check_password($password, $item->password)) {
-                return '<div class="qdr-tss-error">' . __('Invalid password.', 'qdr-temporary-shared-storage') . '</div>' . $this->render_download_form($media_id);
-            }
-            
-            // Verify reference if set
-            if (!empty($item->reference)) {
-                $reference = isset($_POST['qdr_tss_reference']) ? $_POST['qdr_tss_reference'] : '';
-                if ($reference !== $item->reference) {
-                    return '<div class="qdr-tss-error">' . __('Invalid reference.', 'qdr-temporary-shared-storage') . '</div>' . $this->render_download_form($media_id);
-                }
-            }
-            
-            // Update download count and last download time
-            $wpdb->update(
-                $table_name,
-                array(
-                    'download_count' => $item->download_count + 1,
-                    'last_download' => current_time('mysql')
-                ),
-                array('id' => $item->id),
-                array('%d', '%s'),
-                array('%d')
-            );
-            
-            // Initiate download
-            $file_path = QDR_TSS_UPLOADS_DIR . $item->file_path;
-            
-            if (file_exists($file_path)) {
-                // Set headers for download
-                header('Content-Description: File Transfer');
-                header('Content-Type: application/octet-stream');
-                header('Content-Disposition: attachment; filename="' . $item->original_file_name . '"');
-                header('Expires: 0');
-                header('Cache-Control: must-revalidate');
-                header('Pragma: public');
-                header('Content-Length: ' . filesize($file_path));
-                ob_clean();
-                flush();
-                readfile($file_path);
-                exit;
-            } else {
-                return '<div class="qdr-tss-error">' . __('File not found on server.', 'qdr-temporary-shared-storage') . '</div>';
-            }
-        }
-        
-        // Show download form with media information
-        $output = '<div class="qdr-tss-download-info">';
-        $output .= '<h2>' . __('File Information', 'qdr-temporary-shared-storage') . '</h2>';
-        $output .= '<p><strong>' . __('File Name:', 'qdr-temporary-shared-storage') . '</strong> ' . esc_html($item->original_file_name) . '</p>';
-        
-        if (!empty($item->description)) {
-            $output .= '<p><strong>' . __('Description:', 'qdr-temporary-shared-storage') . '</strong> ' . esc_html($item->description) . '</p>';
-        }
-        
-        if (!empty($item->message)) {
-            $output .= '<p><strong>' . __('Message:', 'qdr-temporary-shared-storage') . '</strong> ' . esc_html($item->message) . '</p>';
-        }
-        
-        $output .= '</div>';
-        $output .= $this->render_download_form($media_id);
-        
-        return $output;
-    }
-
-    /**
-     * Render download form.
-     */
-    private function render_download_form($media_id = null) {
-        $form = '<div class="qdr-tss-download-form">';
-        $form .= '<form method="post" action="">';
-        
-        // Add nonce for security
-        $form .= wp_nonce_field('qdr_tss_download', 'qdr_tss_nonce', true, false);
-        
-        if (!$media_id) {
-            $form .= '<div class="qdr-tss-form-group">';
-            $form .= '<label for="qdr_tss_media_id">' . __('Media ID', 'qdr-temporary-shared-storage') . '</label>';
-            $form .= '<input type="text" name="id" id="qdr_tss_media_id" required>';
-            $form .= '</div>';
-        } else {
-            $form .= '<input type="hidden" name="id" value="' . esc_attr($media_id) . '">';
-        }
-        
-        $form .= '<div class="qdr-tss-form-group">';
-        $form .= '<label for="qdr_tss_password">' . __('Password', 'qdr-temporary-shared-storage') . '</label>';
-        $form .= '<input type="password" name="qdr_tss_password" id="qdr_tss_password" required>';
-        $form .= '</div>';
-        
-        $form .= '<div class="qdr-tss-form-group">';
-        $form .= '<label for="qdr_tss_reference">' . __('Reference (if required)', 'qdr-temporary-shared-storage') . '</label>';
-        $form .= '<input type="text" name="qdr_tss_reference" id="qdr_tss_reference">';
-        $form .= '</div>';
-        
-        $form .= '<div class="qdr-tss-form-group">';
-        $form .= '<button type="submit" name="qdr_tss_download_submit" class="qdr-tss-button">' . __('Download File', 'qdr-temporary-shared-storage') . '</button>';
-        $form .= '</div>';
-        
-        $form .= '</form>';
-        $form .= '</div>';
-        
-        return $form;
-    }
+function activate_qdr_tss() {
+    require_once plugin_dir_path(__FILE__) . 'includes/class-qdr-tss-activator.php';
+    QDR_TSS_Activator::activate();
 }
 }
 
 
-// Initialize the plugin
-function qdr_temporary_shared_storage() {
-    return QDR_Temporary_Shared_Storage::get_instance();
+/**
+ * The code that runs during plugin deactivation.
+ */
+function deactivate_qdr_tss() {
+    require_once plugin_dir_path(__FILE__) . 'includes/class-qdr-tss-deactivator.php';
+    QDR_TSS_Deactivator::deactivate();
 }
 }
-qdr_temporary_shared_storage();
+
+register_activation_hook(__FILE__, 'activate_qdr_tss');
+register_deactivation_hook(__FILE__, 'deactivate_qdr_tss');
 
 
 /**
 /**
- * Admin class to handle admin pages
+ * The core plugin class that is used to define internationalization,
+ * admin-specific hooks, and public-facing site hooks.
  */
  */
-class QDR_TSS_Admin {
-    /**
-     * Setup the admin hooks
-     */
-    public static function init() {
-        add_action('wp_ajax_qdr_tss_get_items', array(__CLASS__, 'ajax_get_items'));
-        add_action('wp_ajax_qdr_tss_edit_item', array(__CLASS__, 'ajax_edit_item'));
-        add_action('wp_ajax_qdr_tss_delete_item', array(__CLASS__, 'ajax_delete_item'));
-        add_action('wp_ajax_qdr_tss_save_settings', array(__CLASS__, 'ajax_save_settings'));
-        add_action('wp_ajax_qdr_tss_purge_expired', array(__CLASS__, 'ajax_purge_expired'));
-    }
-    
-    /**
-     * AJAX handler for getting items
-     */
-    public static function ajax_get_items() {
-        // 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')));
-        }
-        
-        global $wpdb;
-        $table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE;
-        
-        // Parse parameters
-        $per_page = isset($_POST['per_page']) ? intval($_POST['per_page']) : 10;
-        $page = isset($_POST['page']) ? intval($_POST['page']) : 1;
-        $offset = ($page - 1) * $per_page;
-        
-        // Build query
-        $query = "SELECT * FROM $table_name";
-        $count_query = "SELECT COUNT(*) FROM $table_name";
-        
-        $where_conditions = array();
-        $query_values = array();
-        
-        // Search
-        if (isset($_POST['search']) && !empty($_POST['search'])) {
-            $search = $_POST['search'];
-            $where_conditions[] = "(original_file_name LIKE %s OR reference LIKE %s)";
-            $query_values[] = '%' . $wpdb->esc_like($search) . '%';
-            $query_values[] = '%' . $wpdb->esc_like($search) . '%';
-        }
-        
-        // Add WHERE conditions if any
-        if (!empty($where_conditions)) {
-            $query .= " WHERE " . implode(" AND ", $where_conditions);
-            $count_query .= " WHERE " . implode(" AND ", $where_conditions);
-        }
-        
-        // Add sorting
-        $orderby = isset($_POST['orderby']) ? $_POST['orderby'] : 'upload_date';
-        $order = isset($_POST['order']) ? $_POST['order'] : 'DESC';
-        
-        $allowed_columns = array(
-            'media_id', 'original_file_name', 'description', 'active_from', 'active_to',
-            'reference', 'upload_date', 'download_count', 'last_download'
-        );
-        
-        if (!in_array($orderby, $allowed_columns)) {
-            $orderby = 'upload_date';
-        }
-        
-        $order = strtoupper($order) === 'ASC' ? 'ASC' : 'DESC';
-        
-        $query .= " ORDER BY $orderby $order";
-        
-        // Add pagination
-        $query .= " LIMIT %d OFFSET %d";
-        $query_values[] = $per_page;
-        $query_values[] = $offset;
-        
-        // Prepare and execute queries
-        $prepared_query = $wpdb->prepare($query, $query_values);
-        $items = $wpdb->get_results($prepared_query);
-        
-        $prepared_count_query = count($query_values) > 2 
-            ? $wpdb->prepare($count_query, array_slice($query_values, 0, -2))
-            : $count_query;
-            
-        $total_items = $wpdb->get_var($prepared_count_query);
-        
-        wp_send_json_success(array(
-            'items' => $items,
-            'total' => intval($total_items),
-            'total_pages' => ceil($total_items / $per_page)
-        ));
-    }
-    
-    /**
-     * AJAX handler for editing an item
-     */
-    public static function ajax_edit_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')));
-        }
-        
-        global $wpdb;
-        $table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE;
-        $id = intval($_POST['id']);
-        
-        // Prepare update data
-        $update_data = array();
-        $update_format = array();
-        
-        if (isset($_POST['description'])) {
-            $update_data['description'] = sanitize_textarea_field($_POST['description']);
-            $update_format[] = '%s';
-        }
-        
-        if (isset($_POST['message'])) {
-            $update_data['message'] = sanitize_textarea_field($_POST['message']);
-            $update_format[] = '%s';
-        }
-        
-        if (isset($_POST['active_from'])) {
-            $update_data['active_from'] = sanitize_text_field($_POST['active_from']);
-            $update_format[] = '%s';
-        }
-        
-        if (isset($_POST['active_to'])) {
-            $update_data['active_to'] = sanitize_text_field($_POST['active_to']);
-            $update_format[] = '%s';
-        }
-        
-        if (isset($_POST['reference'])) {
-            $update_data['reference'] = sanitize_text_field($_POST['reference']);
-            $update_format[] = '%s';
-        }
-        
-        if (empty($update_data)) {
-            wp_send_json_error(array('message' => __('No changes to update.', 'qdr-temporary-shared-storage')));
-        }
-        
-        // Update in database
-        $result = $wpdb->update(
-            $table_name,
-            $update_data,
-            array('id' => $id),
-            $update_format,
-            array('%d')
-        );
-        
-        if ($result === false) {
-            wp_send_json_error(array('message' => __('Failed to update item.', 'qdr-temporary-shared-storage')));
-        }
-        
-        // Get updated record
-        $item = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table_name WHERE id = %d", $id));
-        
-        wp_send_json_success(array(
-            'item' => $item,
-            'message' => __('Item updated successfully.', 'qdr-temporary-shared-storage')
-        ));
-    }
-    
-    /**
-     * AJAX handler for deleting an item
-     */
-    public static function ajax_delete_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')));
-        }
-        
-        global $wpdb;
-        $table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE;
-        $id = intval($_POST['id']);
-        
-        // Get file path
-        $item = $wpdb->get_row($wpdb->prepare(
-            "SELECT file_path FROM $table_name WHERE id = %d",
-            $id
-        ));
-        
-        if (!$item) {
-            wp_send_json_error(array('message' => __('Item not found.', 'qdr-temporary-shared-storage')));
-        }
-        
-        // Delete file
-        $file_path = QDR_TSS_UPLOADS_DIR . $item->file_path;
-        if (file_exists($file_path)) {
-            unlink($file_path);
-        }
-        
-        // Delete from database
-        $result = $wpdb->delete($table_name, array('id' => $id), array('%d'));
-        
-        if ($result === false) {
-            wp_send_json_error(array('message' => __('Failed to delete item.', 'qdr-temporary-shared-storage')));
-        }
-        
-        wp_send_json_success(array(
-            'message' => __('Item deleted successfully.', 'qdr-temporary-shared-storage')
-        ));
-    }
-    
-    /**
-     * AJAX handler for saving settings
-     */
-    public static function ajax_save_settings() {
-        // 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')));
-        }
-        
-        // Get current settings
-        $settings = get_option('qdr_tss_settings', array());
-        
-        // Update settings
-        if (isset($_POST['api_key'])) {
-            $settings['api_key'] = sanitize_text_field($_POST['api_key']);
-        }
-        
-        if (isset($_POST['media_category'])) {
-            $settings['media_category'] = sanitize_text_field($_POST['media_category']);
-        }
-        
-        if (isset($_POST['max_upload_size'])) {
-            $settings['max_upload_size'] = intval($_POST['max_upload_size']);
-        }
-        
-        // Save settings
-        update_option('qdr_tss_settings', $settings);
-        
-        wp_send_json_success(array(
-            'message' => __('Settings saved successfully.', 'qdr-temporary-shared-storage')
-        ));
-    }
-    
-    /**
-     * AJAX handler for purging expired files
-     */
-    public static function ajax_purge_expired() {
-        // 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')));
-        }
-        
-        $plugin = qdr_temporary_shared_storage();
-        $count = $plugin->purge_expired_files();
-        
-        wp_send_json_success(array(
-            'message' => sprintf(
-                _n(
-                    '%d expired file has been purged.',
-                    '%d expired files have been purged.',
-                    $count,
-                    'qdr-temporary-shared-storage'
-                ),
-                $count
-            )
-        ));
-    }
-}
+require plugin_dir_path(__FILE__) . 'includes/class-qdr-tss.php';
 
 
-// Initialize admin class
-QDR_TSS_Admin::init();
+/**
+ * Begins execution of the plugin.
+ *
+ * @since    1.0.0
+ */
+function run_qdr_tss() {
+    $plugin = new QDR_TSS();
+    $plugin->run();
+}
 
 
-// Include admin pages
-require_once QDR_TSS_PLUGIN_DIR . 'admin/media-page.php';
-require_once QDR_TSS_PLUGIN_DIR . 'admin/settings-page.php';
-        
+// Let's go!
+run_qdr_tss();

+ 464 - 115
qdr-temporary-shared-storage/readme.md

@@ -39,80 +39,6 @@ QDR Temporary Shared Storage enables secure sharing of large files through WordP
 - **Media > Shared Storage**: Manage all shared files
 - **Media > Shared Storage**: Manage all shared files
 - **Settings > Shared Storage**: Configure plugin settings
 - **Settings > Shared Storage**: Configure plugin settings
 
 
-### REST API Endpoints
-
-All REST API requests require an API key sent in the `X-Api-Key` header.
-
-#### Upload Files (Chunked)
-
-1. **Initialize upload**:
-```
-POST /wp-json/qdr-tss/v1/upload/init
-```
-Parameters:
-- `original_file_name`: Original file name
-- `file_size`: File size in bytes
-
-2. **Upload chunks**:
-```
-POST /wp-json/qdr-tss/v1/upload/chunk
-```
-Parameters:
-- `upload_id`: Upload ID from initialization
-- `chunk_index`: Index of the current chunk
-- `total_chunks`: Total number of chunks
-- `chunk`: File data (multipart/form-data)
-
-3. **Finalize upload**:
-```
-POST /wp-json/qdr-tss/v1/upload/finalize
-```
-Parameters:
-- `upload_id`: Upload ID from initialization
-- `total_chunks`: Total number of chunks
-- `original_file_name`: Original file name
-- `description`: File description
-- `message`: Custom message (optional)
-- `active_from`: Date from which permalink will be active (optional)
-- `active_to`: Date until which permalink will be active (optional)
-- `password`: Password for download protection
-- `reference`: Custom reference value (optional)
-
-#### Manage Files
-
-- **Get file details**:
-```
-GET /wp-json/qdr-tss/v1/media/{id}
-```
-
-- **Update file properties**:
-```
-PUT /wp-json/qdr-tss/v1/media/{id}
-```
-Parameters:
-- `message`: Custom message (optional)
-- `active_from`: Date from which permalink will be active (optional)
-- `active_to`: Date until which permalink will be active (optional)
-- `reference`: Custom reference value (optional)
-- `description`: File description (optional)
-
-- **Delete file**:
-```
-DELETE /wp-json/qdr-tss/v1/media/{id}
-```
-
-- **Get collection of files**:
-```
-GET /wp-json/qdr-tss/v1/media
-```
-Parameters:
-- `page`: Page number (optional, default: 1)
-- `per_page`: Items per page (optional, default: 10)
-- `orderby`: Field to sort by (optional)
-- `order`: Sort order, ASC or DESC (optional)
-- `original_file_name`: Filter by file name (optional)
-- `reference`: Filter by reference (optional)
-
 ### Shortcode
 ### Shortcode
 
 
 To display a download form for a specific file:
 To display a download form for a specific file:
@@ -127,22 +53,34 @@ To display a generic download form where users can enter an ID, password, and re
 [qdr_tss_download]
 [qdr_tss_download]
 ```
 ```
 
 
-## Configuration Options
+## REST API
 
 
-### API Key
-Generate or specify an API key for REST API authentication.
+The plugin provides a complete REST API for programmatically uploading, managing, and downloading shared files.
 
 
-### Media Category
-Specify a category to assign to uploaded media files.
+### Authentication
 
 
-### Maximum Storage Size
-Set the maximum total storage size for all shared files.
+All API requests require an API key sent in the `X-Api-Key` HTTP header. You can generate or view your API key in the plugin settings page at "Settings > Shared Storage".
+
+### API Endpoints
 
 
-## Example API Usage (PHP)
+#### Upload Endpoints
+
+- **POST /wp-json/qdr-tss/v1/upload/init**: Initialize chunked upload
+- **POST /wp-json/qdr-tss/v1/upload/chunk**: Upload a file chunk
+- **POST /wp-json/qdr-tss/v1/upload/finalize**: Finalize upload and create file record
+
+#### Media Endpoints
+
+- **GET /wp-json/qdr-tss/v1/media**: Get list of shared files
+- **GET /wp-json/qdr-tss/v1/media/{media_id}**: Get single file details
+- **PUT /wp-json/qdr-tss/v1/media/{media_id}**: Update file properties
+- **DELETE /wp-json/qdr-tss/v1/media/{media_id}**: Delete a file
+
+### API Example (PHP)
 
 
 ```php
 ```php
-// Initialize upload
-$ch = curl_init('https://www.quadarax.com/wp-json/qdr-tss/v1/upload/init');
+// Initialize chunked upload
+$ch = curl_init('https://www.example.com/wp-json/qdr-tss/v1/upload/init');
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 curl_setopt($ch, CURLOPT_POST, true);
 curl_setopt($ch, CURLOPT_POST, true);
 curl_setopt($ch, CURLOPT_HTTPHEADER, [
 curl_setopt($ch, CURLOPT_HTTPHEADER, [
@@ -166,36 +104,12 @@ $total_chunks = ceil($total_size / $chunk_size);
 for ($i = 0; $i < $total_chunks; $i++) {
 for ($i = 0; $i < $total_chunks; $i++) {
     $chunk_data = fread($file, $chunk_size);
     $chunk_data = fread($file, $chunk_size);
     
     
-    $ch = curl_init('https://www.quadarax.com/wp-json/qdr-tss/v1/upload/chunk');
-    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-    curl_setopt($ch, CURLOPT_POST, true);
-    curl_setopt($ch, CURLOPT_HTTPHEADER, [
-        'X-Api-Key: your-api-key-here'
-    ]);
-    
-    $post_data = [
-        'upload_id' => $upload_id,
-        'chunk_index' => $i,
-        'total_chunks' => $total_chunks,
-        'chunk' => new CURLFile('php://temp', 'application/octet-stream', 'chunk')
-    ];
-    
-    // Write chunk data to temp file
-    $temp = fopen('php://temp', 'wb+');
-    fwrite($temp, $chunk_data);
-    rewind($temp);
-    
-    curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
-    $response = json_decode(curl_exec($ch));
-    curl_close($ch);
-    
-    fclose($temp);
+    // Upload the chunk
+    // ...
 }
 }
 
 
-fclose($file);
-
 // Finalize upload
 // Finalize upload
-$ch = curl_init('https://www.quadarax.com/wp-json/qdr-tss/v1/upload/finalize');
+$ch = curl_init('https://www.example.com/wp-json/qdr-tss/v1/upload/finalize');
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
 curl_setopt($ch, CURLOPT_POST, true);
 curl_setopt($ch, CURLOPT_POST, true);
 curl_setopt($ch, CURLOPT_HTTPHEADER, [
 curl_setopt($ch, CURLOPT_HTTPHEADER, [
@@ -219,6 +133,445 @@ $media_id = $response->media_id;
 $permalink = $response->permalink;
 $permalink = $response->permalink;
 ```
 ```
 
 
+## REST API Documentation
+
+### Authentication
+
+All API requests require an API key sent in the `X-Api-Key` HTTP header. You can generate or view your API key in the plugin settings page at "Settings > Shared Storage".
+
+### API Base URL
+
+The base URL for all API endpoints is:
+
+```
+https://your-wordpress-site.com/wp-json/qdr-tss/v1/
+```
+
+### Endpoints
+
+#### 1. Initialize Chunked Upload
+
+Prepares the server for a chunked upload.
+
+- **Endpoint**: `/upload/init`
+- **Method**: `POST`
+- **Parameters**:
+  - `original_file_name`: The original name of the file being uploaded
+  - `file_size`: The total size of the file in bytes
+
+- **Response**:
+  ```json
+  {
+    "upload_id": "5f8d7a6b9c3e",
+    "status": "initialized"
+  }
+  ```
+
+#### 2. Upload Chunk
+
+Uploads a single chunk of the file.
+
+- **Endpoint**: `/upload/chunk`
+- **Method**: `POST`
+- **Parameters**:
+  - `upload_id`: The upload ID received from the initialization
+  - `chunk_index`: The index of the current chunk (starting from 0)
+  - `total_chunks`: The total number of chunks
+  - `chunk`: The chunk file data (multipart/form-data)
+
+- **Response**:
+  ```json
+  {
+    "upload_id": "5f8d7a6b9c3e",
+    "chunk_index": 0,
+    "status": "chunk_uploaded"
+  }
+  ```
+
+#### 3. Finalize Upload
+
+Combines all chunks and creates the file record.
+
+- **Endpoint**: `/upload/finalize`
+- **Method**: `POST`
+- **Parameters**:
+  - `upload_id`: The upload ID received from the initialization
+  - `total_chunks`: The total number of chunks
+  - `original_file_name`: The original name of the file
+  - `description`: Description of the file
+  - `message`: Custom message (optional)
+  - `active_from`: Date from which permalink will be active (optional, defaults to current time)
+  - `active_to`: Date until which permalink will be active (optional, defaults to 30 days from now)
+  - `password`: Password for download protection
+  - `reference`: Custom reference value (optional)
+
+- **Response**:
+  ```json
+  {
+    "media_id": "1621234567",
+    "permalink": "https://your-wordpress-site.com/shared-file-download/1621234567",
+    "shortcode": "[qdr_tss_download id=\"1621234567\"]"
+  }
+  ```
+
+#### 4. Get File Details
+
+Retrieves details about a specific file.
+
+- **Endpoint**: `/media/{media_id}`
+- **Method**: `GET`
+
+- **Response**:
+  ```json
+  {
+    "id": 1,
+    "media_id": "1621234567",
+    "original_file_name": "example.pdf",
+    "description": "Example document",
+    "message": "Please review this document",
+    "active_from": "2025-05-19 15:30:00",
+    "active_to": "2025-06-18 15:30:00",
+    "reference": "REF123",
+    "upload_date": "2025-05-19 15:30:00",
+    "download_count": 5,
+    "last_download": "2025-05-20 10:45:30",
+    "file_size": 1048576
+  }
+  ```
+
+#### 5. Update File
+
+Updates properties of a specific file.
+
+- **Endpoint**: `/media/{media_id}`
+- **Method**: `PUT`
+- **Parameters**:
+  - `message`: Custom message (optional)
+  - `active_from`: Date from which permalink will be active (optional)
+  - `active_to`: Date until which permalink will be active (optional)
+  - `reference`: Custom reference value (optional)
+  - `description`: File description (optional)
+
+- **Response**: Updated file details (same format as Get File Details)
+
+#### 6. Delete File
+
+Deletes a specific file.
+
+- **Endpoint**: `/media/{media_id}`
+- **Method**: `DELETE`
+
+- **Response**:
+  ```json
+  {
+    "deleted": true,
+    "media_id": "1621234567"
+  }
+  ```
+
+#### 7. List Files
+
+Retrieves a list of files with pagination and filtering.
+
+- **Endpoint**: `/media`
+- **Method**: `GET`
+- **Parameters**:
+  - `page`: Page number (optional, default: 1)
+  - `per_page`: Items per page (optional, default: 10)
+  - `orderby`: Field to sort by (optional, default: "upload_date")
+  - `order`: Sort order (optional, default: "DESC")
+  - `original_file_name`: Filter by file name (optional)
+  - `reference`: Filter by reference (optional)
+
+- **Response**:
+  ```json
+  [
+    {
+      "id": 1,
+      "media_id": "1621234567",
+      "original_file_name": "example.pdf",
+      "description": "Example document",
+      "message": "Please review this document",
+      "active_from": "2025-05-19 15:30:00",
+      "active_to": "2025-06-18 15:30:00",
+      "reference": "REF123",
+      "upload_date": "2025-05-19 15:30:00",
+      "download_count": 5,
+      "last_download": "2025-05-20 10:45:30",
+      "file_size": 1048576
+    },
+    ...
+  ]
+  ```
+
+### Code Examples
+
+#### PHP Example: Complete File Upload
+
+```php
+<?php
+/**
+ * Example of uploading a file using chunked upload
+ */
+function uploadSharedFile($file_path, $description, $password, $reference = '') {
+    $api_key = 'your-api-key-here';
+    $api_url = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
+    
+    // File information
+    $file_name = basename($file_path);
+    $file_size = filesize($file_path);
+    $chunk_size = 1024 * 1024; // 1MB chunks
+    $total_chunks = ceil($file_size / $chunk_size);
+    
+    // Step 1: Initialize upload
+    $ch = curl_init($api_url . '/upload/init');
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+    curl_setopt($ch, CURLOPT_POST, true);
+    curl_setopt($ch, CURLOPT_HTTPHEADER, [
+        'X-Api-Key: ' . $api_key,
+        'Content-Type: application/json'
+    ]);
+    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
+        'original_file_name' => $file_name,
+        'file_size' => $file_size
+    ]));
+    
+    $response = curl_exec($ch);
+    curl_close($ch);
+    
+    $init_result = json_decode($response, true);
+    if (!isset($init_result['upload_id'])) {
+        throw new Exception('Failed to initialize upload: ' . $response);
+    }
+    
+    $upload_id = $init_result['upload_id'];
+    
+    // Step 2: Upload chunks
+    $file = fopen($file_path, 'rb');
+    
+    for ($i = 0; $i < $total_chunks; $i++) {
+        // Create a temporary file for the chunk
+        $chunk_file = tempnam(sys_get_temp_dir(), 'chunk');
+        $chunk_handle = fopen($chunk_file, 'wb');
+        
+        // Read chunk from original file
+        $chunk_data = fread($file, $chunk_size);
+        fwrite($chunk_handle, $chunk_data);
+        fclose($chunk_handle);
+        
+        // Upload the chunk
+        $ch = curl_init($api_url . '/upload/chunk');
+        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+        curl_setopt($ch, CURLOPT_POST, true);
+        curl_setopt($ch, CURLOPT_HTTPHEADER, [
+            'X-Api-Key: ' . $api_key
+        ]);
+        
+        $post_data = [
+            'upload_id' => $upload_id,
+            'chunk_index' => $i,
+            'total_chunks' => $total_chunks,
+            'chunk' => new CURLFile($chunk_file, 'application/octet-stream', 'chunk')
+        ];
+        
+        curl_setopt($ch, CURLOPT_POSTFIELDS, $post_data);
+        $response = curl_exec($ch);
+        curl_close($ch);
+        
+        // Delete temporary chunk file
+        unlink($chunk_file);
+        
+        $chunk_result = json_decode($response, true);
+        if (!isset($chunk_result['status']) || $chunk_result['status'] !== 'chunk_uploaded') {
+            throw new Exception('Failed to upload chunk ' . $i . ': ' . $response);
+        }
+        
+        // Display progress
+        echo "Uploaded chunk " . ($i + 1) . " of " . $total_chunks . "\n";
+    }
+    
+    fclose($file);
+    
+    // Step 3: Finalize upload
+    $ch = curl_init($api_url . '/upload/finalize');
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
+    curl_setopt($ch, CURLOPT_POST, true);
+    curl_setopt($ch, CURLOPT_HTTPHEADER, [
+        'X-Api-Key: ' . $api_key,
+        'Content-Type: application/json'
+    ]);
+    
+    // Set expiration to 30 days from now
+    $active_to = date('Y-m-d H:i:s', strtotime('+30 days'));
+    
+    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode([
+        'upload_id' => $upload_id,
+        'total_chunks' => $total_chunks,
+        'original_file_name' => $file_name,
+        'description' => $description,
+        'message' => 'Uploaded via API example',
+        'password' => $password,
+        'reference' => $reference,
+        'active_to' => $active_to
+    ]));
+    
+    $response = curl_exec($ch);
+    curl_close($ch);
+    
+    $final_result = json_decode($response, true);
+    if (!isset($final_result['media_id'])) {
+        throw new Exception('Failed to finalize upload: ' . $response);
+    }
+    
+    return $final_result;
+}
+```
+
+#### JavaScript Example: Chunked File Upload
+
+```javascript
+/**
+ * Uploads a file using chunked upload
+ * @param {File} file The file to upload
+ * @param {string} description File description
+ * @param {string} password Password for download protection
+ * @param {string} reference Optional reference
+ * @param {function} progressCallback Callback for upload progress
+ * @returns {Promise} Promise resolving to the upload result
+ */
+async function uploadSharedFile(file, description, password, reference = '', progressCallback = null) {
+    const apiKey = 'your-api-key-here';
+    const apiUrl = 'https://your-wordpress-site.com/wp-json/qdr-tss/v1';
+    
+    const chunkSize = 1024 * 1024; // 1MB chunks
+    const totalChunks = Math.ceil(file.size / chunkSize);
+    
+    // Step 1: Initialize upload
+    const initResponse = await fetch(`${apiUrl}/upload/init`, {
+        method: 'POST',
+        headers: {
+            'X-Api-Key': apiKey,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({
+            original_file_name: file.name,
+            file_size: file.size
+        })
+    });
+    
+    const initResult = await initResponse.json();
+    if (!initResult.upload_id) {
+        throw new Error('Failed to initialize upload');
+    }
+    
+    const uploadId = initResult.upload_id;
+    
+    // Step 2: Upload chunks
+    for (let i = 0; i < totalChunks; i++) {
+        const start = i * chunkSize;
+        const end = Math.min(file.size, start + chunkSize);
+        const chunk = file.slice(start, end);
+        
+        const formData = new FormData();
+        formData.append('upload_id', uploadId);
+        formData.append('chunk_index', i);
+        formData.append('total_chunks', totalChunks);
+        formData.append('chunk', chunk);
+        
+        const chunkResponse = await fetch(`${apiUrl}/upload/chunk`, {
+            method: 'POST',
+            headers: {
+                'X-Api-Key': apiKey
+            },
+            body: formData
+        });
+        
+        const chunkResult = await chunkResponse.json();
+        if (!chunkResult.status || chunkResult.status !== 'chunk_uploaded') {
+            throw new Error(`Failed to upload chunk ${i}`);
+        }
+        
+        // Update progress
+        if (progressCallback) {
+            progressCallback((i + 1) / totalChunks * 100);
+        }
+    }
+    
+    // Step 3: Finalize upload
+    const activeToDate = new Date();
+    activeToDate.setDate(activeToDate.getDate() + 30); // 30 days from now
+    const activeTo = activeToDate.toISOString().slice(0, 19).replace('T', ' ');
+    
+    const finalizeResponse = await fetch(`${apiUrl}/upload/finalize`, {
+        method: 'POST',
+        headers: {
+            'X-Api-Key': apiKey,
+            'Content-Type': 'application/json'
+        },
+        body: JSON.stringify({
+            upload_id: uploadId,
+            total_chunks: totalChunks,
+            original_file_name: file.name,
+            description: description,
+            message: 'Uploaded via JavaScript API example',
+            password: password,
+            reference: reference,
+            active_to: activeTo
+        })
+    });
+    
+    const finalResult = await finalizeResponse.json();
+    if (!finalResult.media_id) {
+        throw new Error('Failed to finalize upload');
+    }
+    
+    return finalResult;
+}
+```
+
+### Error Handling
+
+All API endpoints return standard HTTP status codes:
+
+- `200 OK`: Request succeeded
+- `201 Created`: Resource created successfully
+- `400 Bad Request`: Invalid request parameters
+- `401 Unauthorized`: Missing API key
+- `403 Forbidden`: Invalid API key
+- `404 Not Found`: Resource not found
+- `500 Internal Server Error`: Server error
+
+Error responses include a message explaining the error:
+
+```json
+{
+  "code": "qdr_tss_not_found",
+  "message": "Media not found.",
+  "data": {
+    "status": 404
+  }
+}
+```
+
+### Best Practices
+
+1. Always use chunked uploads for large files
+2. Set reasonable expiration dates for files to avoid cluttering storage
+3. Use unique reference codes to help identify files
+4. Regularly check and purge expired files using the admin interface
+5. Include descriptive file names and messages for better user experience
+
+## Configuration Options
+
+### API Key
+Generate or specify an API key for REST API authentication.
+
+### Media Category
+Specify a category to assign to uploaded media files.
+
+### Maximum Storage Size
+Set the maximum total storage size for all shared files.
+
 ## Security Considerations
 ## Security Considerations
 
 
 - All shared files are password-protected
 - All shared files are password-protected
@@ -233,7 +586,3 @@ GPL v2 or later - https://www.gnu.org/licenses/gpl-2.0.html
 ## Author
 ## Author
 
 
 [Dalibor Votruba](https://www.quadarax.com)
 [Dalibor Votruba](https://www.quadarax.com)
-
-## Support
-
-For support or feature requests, please visit [QDR Plugin Page](https://www.quadarax.com/plugins/qdr-temporary-shared-storage).

+ 0 - 85
qdr-temporary-shared-storage/readme.txt

@@ -1,85 +0,0 @@
-=== QDR Temporary Shared Storage ===
-Contributors: qdr
-Tags: file sharing, media, rest api, temporary storage, password protection
-Requires at least: 5.0
-Tested up to: 6.4
-Requires PHP: 7.2
-Stable tag: 1.0.0
-License: GPLv2 or later
-License URI: http://www.gnu.org/licenses/gpl-2.0.html
-
-WordPress plugin for temporary file sharing with REST API support and password protection.
-
-== Description ==
-
-QDR Temporary Shared Storage extends WordPress functionality by providing a REST API interface to upload temporary media files and share them via password-protected download links.
-
-= Key Features =
-
-* Upload large media files via REST API with chunked uploads
-* Password protection for all shared files
-* Automatic expiration of shared files
-* Download count tracking
-* Admin interface for managing shared files
-* Configurable storage limits
-* REST API for integration with external applications
-
-= REST API Endpoints =
-
-* `POST /wp-json/qdr-tss/v1/upload/init` - Initialize chunked upload
-* `POST /wp-json/qdr-tss/v1/upload/chunk` - Upload a file chunk
-* `POST /wp-json/qdr-tss/v1/upload/finalize` - Finalize upload and create shared file
-* `GET /wp-json/qdr-tss/v1/media/{id}` - Get shared file details
-* `PUT /wp-json/qdr-tss/v1/media/{id}` - Update shared file properties
-* `DELETE /wp-json/qdr-tss/v1/media/{id}` - Delete shared file
-* `GET /wp-json/qdr-tss/v1/media` - Get collection of shared files
-
-= Translations =
-
-* English
-
-== Installation ==
-
-1. Upload the plugin files to the `/wp-content/plugins/qdr-temporary-shared-storage` directory, or install the plugin through the WordPress plugins screen directly.
-2. Activate the plugin through the 'Plugins' screen in WordPress.
-3. Configure the plugin settings in 'Settings > Shared Storage'.
-
-== Frequently Asked Questions ==
-
-= How do I use the REST API? =
-
-You need to include the API key in your requests as an HTTP header:
-
-```
-X-Api-Key: your-api-key-here
-```
-
-See the plugin settings page to view or generate your API key.
-
-= How large can uploaded files be? =
-
-The plugin supports files of any size through chunked uploads. The maximum total storage size can be configured in the settings.
-
-= How long are files kept? =
-
-Each file has configurable 'Active From' and 'Active To' dates. Files are automatically deleted after their expiration date. By default, files expire 30 days after upload.
-
-= Can I manually delete expired files? =
-
-Yes, you can manually purge expired files from the Settings > Shared Storage page.
-
-== Screenshots ==
-
-1. Admin interface for managing shared files
-2. Settings page
-3. Download page
-
-== Changelog ==
-
-= 1.0.0 =
-* Initial release
-
-== Upgrade Notice ==
-
-= 1.0.0 =
-Initial release

+ 284 - 0
qdr-temporary-shared-storage/rest-api/class-qdr-tss-media-controller.php

@@ -0,0 +1,284 @@
+<?php
+/**
+ * Media REST API Controller for the plugin.
+ *
+ * This class handles REST API endpoints for media operations.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+class QDR_TSS_Media_Controller {
+
+    /**
+     * The plugin name.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $plugin_name    The name of this plugin.
+     */
+    private $plugin_name;
+
+    /**
+     * The plugin version.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $version    The version of this plugin.
+     */
+    private $version;
+
+    /**
+     * The namespace for the REST API routes.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $namespace    The namespace for the REST API routes.
+     */
+    private $namespace = 'qdr-tss/v1';
+
+    /**
+     * The base for the REST API routes.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $base    The base for the REST API routes.
+     */
+    private $base = 'media';
+
+    /**
+     * The database manager instance.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      QDR_TSS_DB_Manager    $db_manager    The database manager instance.
+     */
+    private $db_manager;
+
+    /**
+     * The file manager instance.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      QDR_TSS_File_Manager    $file_manager    The file manager instance.
+     */
+    private $file_manager;
+
+    /**
+     * Initialize the class and set its properties.
+     *
+     * @since    1.0.0
+     * @param    string    $plugin_name    The name of this plugin.
+     * @param    string    $version        The version of this plugin.
+     */
+    public function __construct($plugin_name, $version) {
+        $this->plugin_name = $plugin_name;
+        $this->version = $version;
+        $this->db_manager = QDR_TSS_DB_Manager::instance();
+        $this->file_manager = QDR_TSS_File_Manager::instance();
+    }
+
+    /**
+     * Register the REST API routes.
+     *
+     * @since    1.0.0
+     */
+    public function register_routes() {
+        register_rest_route($this->namespace, '/' . $this->base, array(
+            array(
+                'methods' => WP_REST_Server::READABLE,
+                'callback' => array($this, 'get_items'),
+                'permission_callback' => array($this, 'check_permission'),
+            ),
+        ));
+        
+        register_rest_route($this->namespace, '/' . $this->base . '/(?P<id>[\d]+)', array(
+            array(
+                'methods' => WP_REST_Server::READABLE,
+                'callback' => array($this, 'get_item'),
+                'permission_callback' => array($this, 'check_permission'),
+            ),
+            array(
+                'methods' => WP_REST_Server::EDITABLE,
+                'callback' => array($this, 'update_item'),
+                'permission_callback' => array($this, 'check_permission'),
+            ),
+            array(
+                'methods' => WP_REST_Server::DELETABLE,
+                'callback' => array($this, 'delete_item'),
+                'permission_callback' => array($this, 'check_permission'),
+            ),
+        ));
+    }
+
+    /**
+     * Check permission for REST API requests.
+     *
+     * @since    1.0.0
+     * @return   bool    True if authorized, false otherwise.
+     */
+    public function check_permission() {
+        $rest_controller = new QDR_TSS_REST_Controller($this->plugin_name, $this->version);
+        return $rest_controller->check_rest_permission();
+    }
+
+    /**
+     * Get a collection of items.
+     *
+     * @since    1.0.0
+     * @param    WP_REST_Request    $request    Full details about the request.
+     * @return   WP_REST_Response|WP_Error      Response object on success, or WP_Error object on failure.
+     */
+    public function get_items($request) {
+        $args = array(
+            'per_page' => isset($request['per_page']) ? (int) $request['per_page'] : 10,
+            'page' => isset($request['page']) ? (int) $request['page'] : 1,
+            'orderby' => isset($request['orderby']) ? $request['orderby'] : 'upload_date',
+            'order' => isset($request['order']) ? $request['order'] : 'DESC',
+            'original_file_name' => isset($request['original_file_name']) ? $request['original_file_name'] : '',
+            'reference' => isset($request['reference']) ? $request['reference'] : '',
+        );
+        
+        $result = $this->db_manager->get_items($args);
+        
+        $response = new WP_REST_Response($result['items']);
+        $response->header('X-WP-Total', $result['total']);
+        $response->header('X-WP-TotalPages', $result['total_pages']);
+        
+        return $response;
+    }
+
+    /**
+     * Get a single item.
+     *
+     * @since    1.0.0
+     * @param    WP_REST_Request    $request    Full details about the request.
+     * @return   WP_REST_Response|WP_Error      Response object on success, or WP_Error object on failure.
+     */
+    public function get_item($request) {
+        $id = (int) $request['id'];
+        
+        $item = $this->db_manager->get_item_by_media_id($id);
+        
+        if (!$item) {
+            return new WP_Error(
+                'qdr_tss_not_found',
+                __('Media not found.', 'qdr-temporary-shared-storage'),
+                array('status' => 404)
+            );
+        }
+        
+        // Remove password from response
+        unset($item->password);
+        
+        return new WP_REST_Response($item, 200);
+    }
+
+    /**
+     * Update a single item.
+     *
+     * @since    1.0.0
+     * @param    WP_REST_Request    $request    Full details about the request.
+     * @return   WP_REST_Response|WP_Error      Response object on success, or WP_Error object on failure.
+     */
+    public function update_item($request) {
+        $id = (int) $request['id'];
+        
+        $item = $this->db_manager->get_item_by_media_id($id);
+        
+        if (!$item) {
+            return new WP_Error(
+                'qdr_tss_not_found',
+                __('Media not found.', 'qdr-temporary-shared-storage'),
+                array('status' => 404)
+            );
+        }
+        
+        $data = array();
+        
+        // Update properties
+        if (isset($request['message'])) {
+            $data['message'] = sanitize_textarea_field($request['message']);
+        }
+        
+        if (isset($request['active_from'])) {
+            $data['active_from'] = sanitize_text_field($request['active_from']);
+        }
+        
+        if (isset($request['active_to'])) {
+            $data['active_to'] = sanitize_text_field($request['active_to']);
+        }
+        
+        if (isset($request['reference'])) {
+            $data['reference'] = sanitize_text_field($request['reference']);
+        }
+        
+        if (isset($request['description'])) {
+            $data['description'] = sanitize_textarea_field($request['description']);
+        }
+        
+        if (empty($data)) {
+            return new WP_Error(
+                'qdr_tss_no_changes',
+                __('No changes to update.', 'qdr-temporary-shared-storage'),
+                array('status' => 400)
+            );
+        }
+        
+        $result = $this->db_manager->update_item_by_media_id($id, $data);
+        
+        if (!$result) {
+            return new WP_Error(
+                'qdr_tss_update_failed',
+                __('Failed to update media.', 'qdr-temporary-shared-storage'),
+                array('status' => 500)
+            );
+        }
+        
+        $updated_item = $this->db_manager->get_item_by_media_id($id);
+        
+        // Remove password from response
+        unset($updated_item->password);
+        
+        return new WP_REST_Response($updated_item, 200);
+    }
+
+    /**
+     * Delete a single item.
+     *
+     * @since    1.0.0
+     * @param    WP_REST_Request    $request    Full details about the request.
+     * @return   WP_REST_Response|WP_Error      Response object on success, or WP_Error object on failure.
+     */
+    public function delete_item($request) {
+        $id = (int) $request['id'];
+        
+        $item = $this->db_manager->get_item_by_media_id($id);
+        
+        if (!$item) {
+            return new WP_Error(
+                'qdr_tss_not_found',
+                __('Media not found.', 'qdr-temporary-shared-storage'),
+                array('status' => 404)
+            );
+        }
+        
+        // Delete file
+        $this->file_manager->delete_file($item->file_path);
+        
+        // Delete from database
+        $result = $this->db_manager->delete_item_by_media_id($id);
+        
+        if (!$result) {
+            return new WP_Error(
+                'qdr_tss_delete_failed',
+                __('Failed to delete media.', 'qdr-temporary-shared-storage'),
+                array('status' => 500)
+            );
+        }
+        
+        return new WP_REST_Response(array(
+            'deleted' => true,
+            'media_id' => $id
+        ), 200);
+    }
+}

+ 132 - 0
qdr-temporary-shared-storage/rest-api/class-qdr-tss-rest-controller.php

@@ -0,0 +1,132 @@
+<?php
+/**
+ * Base REST API Controller for the plugin.
+ *
+ * This class handles base REST API functionality, including authentication
+ * and permissions.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+class QDR_TSS_REST_Controller {
+
+    /**
+     * The plugin name.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $plugin_name    The name of this plugin.
+     */
+    private $plugin_name;
+
+    /**
+     * The plugin version.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $version    The version of this plugin.
+     */
+    private $version;
+
+    /**
+     * The settings instance.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      QDR_TSS_Settings    $settings    The settings instance.
+     */
+    private $settings;
+
+    /**
+     * Initialize the class and set its properties.
+     *
+     * @since    1.0.0
+     * @param    string    $plugin_name    The name of this plugin.
+     * @param    string    $version        The version of this plugin.
+     */
+    public function __construct($plugin_name, $version) {
+        $this->plugin_name = $plugin_name;
+        $this->version = $version;
+        $this->settings = QDR_TSS_Settings::instance();
+    }
+
+    /**
+     * Register the REST API routes.
+     *
+     * @since    1.0.0
+     */
+    public function register_routes() {
+        // Media Controller
+        $media_controller = new QDR_TSS_Media_Controller($this->plugin_name, $this->version);
+        $media_controller->register_routes();
+        
+        // Upload Controller
+        $upload_controller = new QDR_TSS_Upload_Controller($this->plugin_name, $this->version);
+        $upload_controller->register_routes();
+    }
+
+    /**
+     * Check API key for REST API requests.
+     *
+     * @since    1.0.0
+     * @param    WP_Error|null    $result    The authentication result so far.
+     * @return   WP_Error|null               The authentication result.
+     */
+    public function check_api_key($result) {
+        // Only check for our namespace
+        if (empty($result) && strpos($_SERVER['REQUEST_URI'], '/wp-json/qdr-tss/') !== false) {
+            $headers = $this->get_all_headers();
+            $api_key = isset($headers['X-Api-Key']) ? $headers['X-Api-Key'] : '';
+            
+            if (!$this->settings->validate_api_key($api_key)) {
+                return new WP_Error(
+                    'rest_forbidden',
+                    __('Invalid API key.', 'qdr-temporary-shared-storage'),
+                    array('status' => 403)
+                );
+            }
+        }
+        
+        return $result;
+    }
+
+    /**
+     * Check REST API permissions.
+     *
+     * @since    1.0.0
+     * @return   bool    True if authorized, false otherwise.
+     */
+    public function check_rest_permission() {
+        $headers = $this->get_all_headers();
+        $api_key = isset($headers['X-Api-Key']) ? $headers['X-Api-Key'] : '';
+        
+        return $this->settings->validate_api_key($api_key);
+    }
+
+    /**
+     * Get all headers.
+     *
+     * @since    1.0.0
+     * @return   array    All headers.
+     */
+    private function get_all_headers() {
+        if (function_exists('getallheaders')) {
+            return getallheaders();
+        }
+        
+        // Manually get headers if getallheaders() is not available
+        $headers = array();
+        foreach ($_SERVER as $name => $value) {
+            if (substr($name, 0, 5) === 'HTTP_') {
+                $name = str_replace(' ', '-', ucwords(strtolower(str_replace('_', ' ', substr($name, 5)))));
+                $headers[$name] = $value;
+            } elseif ($name === 'CONTENT_TYPE') {
+                $headers['Content-Type'] = $value;
+            } elseif ($name === 'CONTENT_LENGTH') {
+                $headers['Content-Length'] = $value;
+            }
+        }
+        
+        return $headers;
+    }
+}

+ 328 - 0
qdr-temporary-shared-storage/rest-api/class-qdr-tss-upload-controller.php

@@ -0,0 +1,328 @@
+<?php
+/**
+ * Upload REST API Controller for the plugin.
+ *
+ * This class handles REST API endpoints for file upload operations.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+class QDR_TSS_Upload_Controller {
+
+    /**
+     * The plugin name.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $plugin_name    The name of this plugin.
+     */
+    private $plugin_name;
+
+    /**
+     * The plugin version.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $version    The version of this plugin.
+     */
+    private $version;
+
+    /**
+     * The namespace for the REST API routes.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      string    $namespace    The namespace for the REST API routes.
+     */
+    private $namespace = 'qdr-tss/v1';
+
+    /**
+     * The database manager instance.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      QDR_TSS_DB_Manager    $db_manager    The database manager instance.
+     */
+    private $db_manager;
+
+    /**
+     * The file manager instance.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      QDR_TSS_File_Manager    $file_manager    The file manager instance.
+     */
+    private $file_manager;
+
+    /**
+     * The settings instance.
+     *
+     * @since    1.0.0
+     * @access   private
+     * @var      QDR_TSS_Settings    $settings    The settings instance.
+     */
+    private $settings;
+
+    /**
+     * Initialize the class and set its properties.
+     *
+     * @since    1.0.0
+     * @param    string    $plugin_name    The name of this plugin.
+     * @param    string    $version        The version of this plugin.
+     */
+    public function __construct($plugin_name, $version) {
+        $this->plugin_name = $plugin_name;
+        $this->version = $version;
+        $this->db_manager = QDR_TSS_DB_Manager::instance();
+        $this->file_manager = QDR_TSS_File_Manager::instance();
+        $this->settings = QDR_TSS_Settings::instance();
+    }
+
+    /**
+     * Register the REST API routes.
+     *
+     * @since    1.0.0
+     */
+    public function register_routes() {
+        // Chunked upload endpoints
+        register_rest_route($this->namespace, '/upload/init', array(
+            'methods' => WP_REST_Server::CREATABLE,
+            'callback' => array($this, 'init_chunked_upload'),
+            'permission_callback' => array($this, 'check_permission'),
+        ));
+        
+        register_rest_route($this->namespace, '/upload/chunk', array(
+            'methods' => WP_REST_Server::CREATABLE,
+            'callback' => array($this, 'upload_chunk'),
+            'permission_callback' => array($this, 'check_permission'),
+        ));
+        
+        register_rest_route($this->namespace, '/upload/finalize', array(
+            'methods' => WP_REST_Server::CREATABLE,
+            'callback' => array($this, 'finalize_chunked_upload'),
+            'permission_callback' => array($this, 'check_permission'),
+        ));
+    }
+
+    /**
+     * Check permission for REST API requests.
+     *
+     * @since    1.0.0
+     * @return   bool    True if authorized, false otherwise.
+     */
+    public function check_permission() {
+        $rest_controller = new QDR_TSS_REST_Controller($this->plugin_name, $this->version);
+        return $rest_controller->check_rest_permission();
+    }
+
+    /**
+     * Initialize chunked upload.
+     *
+     * @since    1.0.0
+     * @param    WP_REST_Request    $request    Full details about the request.
+     * @return   WP_REST_Response|WP_Error      Response object on success, or WP_Error object on failure.
+     */
+    public function init_chunked_upload($request) {
+        // Check required parameters
+        if (!isset($request['original_file_name']) || !isset($request['file_size'])) {
+            return new WP_Error(
+                'qdr_tss_missing_params',
+                __('Missing required parameters.', 'qdr-temporary-shared-storage'),
+                array('status' => 400)
+            );
+        }
+        
+        $file_size = (int) $request['file_size'];
+        $max_upload_size = $this->settings->get_setting('max_upload_size');
+        
+        // Check max upload size
+        if ($file_size > $max_upload_size) {
+            return new WP_Error(
+                'qdr_tss_file_too_large',
+                __('File exceeds maximum upload size.', 'qdr-temporary-shared-storage'),
+                array('status' => 400)
+            );
+        }
+        
+        // Check total storage usage
+        if ($this->db_manager->would_exceed_storage_limit($file_size, $max_upload_size)) {
+            return new WP_Error(
+                'qdr_tss_storage_limit',
+                __('Storage limit exceeded.', 'qdr-temporary-shared-storage'),
+                array('status' => 400)
+            );
+        }
+        
+        // Initialize chunked upload
+        $upload_id = $this->file_manager->init_chunked_upload();
+        
+        if (!$upload_id) {
+            return new WP_Error(
+                'qdr_tss_init_failed',
+                __('Failed to initialize upload.', 'qdr-temporary-shared-storage'),
+                array('status' => 500)
+            );
+        }
+        
+        return new WP_REST_Response(array(
+            'upload_id' => $upload_id,
+            'status' => 'initialized'
+        ), 200);
+    }
+
+    /**
+     * Upload a chunk.
+     *
+     * @since    1.0.0
+     * @param    WP_REST_Request    $request    Full details about the request.
+     * @return   WP_REST_Response|WP_Error      Response object on success, or WP_Error object on failure.
+     */
+    public function upload_chunk($request) {
+        // Check required parameters
+        if (!isset($request['upload_id']) || !isset($request['chunk_index']) || !isset($request['total_chunks'])) {
+            return new WP_Error(
+                'qdr_tss_missing_params',
+                __('Missing required parameters.', 'qdr-temporary-shared-storage'),
+                array('status' => 400)
+            );
+        }
+        
+        $upload_id = $request['upload_id'];
+        $chunk_index = (int) $request['chunk_index'];
+        $total_chunks = (int) $request['total_chunks'];
+        
+        // Handle file upload
+        $files = $request->get_file_params();
+        
+        if (empty($files) || !isset($files['chunk'])) {
+            return new WP_Error(
+                'qdr_tss_no_file',
+                __('No file uploaded.', 'qdr-temporary-shared-storage'),
+                array('status' => 400)
+            );
+        }
+        
+        $chunk_file = $files['chunk'];
+        
+        // Store chunk
+        $result = $this->file_manager->store_chunk($upload_id, $chunk_index, $chunk_file);
+        
+        if (!$result) {
+            return new WP_Error(
+                'qdr_tss_chunk_failed',
+                __('Failed to upload chunk.', 'qdr-temporary-shared-storage'),
+                array('status' => 500)
+            );
+        }
+        
+        return new WP_REST_Response(array(
+            'upload_id' => $upload_id,
+            'chunk_index' => $chunk_index,
+            'status' => 'chunk_uploaded'
+        ), 200);
+    }
+
+    /**
+     * Finalize chunked upload.
+     *
+     * @since    1.0.0
+     * @param    WP_REST_Request    $request    Full details about the request.
+     * @return   WP_REST_Response|WP_Error      Response object on success, or WP_Error object on failure.
+     */
+    public function finalize_chunked_upload($request) {
+        // Check required parameters
+        if (!isset($request['upload_id']) || !isset($request['total_chunks'])) {
+            return new WP_Error(
+                'qdr_tss_missing_params',
+                __('Missing required parameters.', 'qdr-temporary-shared-storage'),
+                array('status' => 400)
+            );
+        }
+        
+        $upload_id = $request['upload_id'];
+        $total_chunks = (int) $request['total_chunks'];
+        
+        // Check for required file metadata
+        $required_fields = array('original_file_name', 'description', 'password');
+        foreach ($required_fields as $field) {
+            if (!isset($request[$field]) || empty($request[$field])) {
+                return new WP_Error(
+                    'qdr_tss_missing_field',
+                    sprintf(__('Missing required field: %s', 'qdr-temporary-shared-storage'), $field),
+                    array('status' => 400)
+                );
+            }
+        }
+        
+        // Check that all chunks are present
+        if (!$this->file_manager->check_all_chunks($upload_id, $total_chunks)) {
+            return new WP_Error(
+                'qdr_tss_missing_chunks',
+                __('Not all chunks have been uploaded.', 'qdr-temporary-shared-storage'),
+                array('status' => 400)
+            );
+        }
+        
+        // Finalize upload
+        $result = $this->file_manager->finalize_chunked_upload(
+            $upload_id,
+            $total_chunks,
+            $request['original_file_name']
+        );
+        
+        if (!$result) {
+            return new WP_Error(
+                'qdr_tss_finalize_failed',
+                __('Failed to finalize upload.', 'qdr-temporary-shared-storage'),
+                array('status' => 500)
+            );
+        }
+        
+        $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 media ID
+        $media_id = time() . rand(1000, 9999);
+        
+        // Insert into database
+        $data = array(
+            'media_id' => $media_id,
+            'original_file_name' => sanitize_file_name($request['original_file_name']),
+            'description' => sanitize_textarea_field($request['description']),
+            'message' => isset($request['message']) ? sanitize_textarea_field($request['message']) : '',
+            'active_from' => $active_from,
+            'active_to' => $active_to,
+            'password' => wp_hash_password($request['password']),
+            'reference' => isset($request['reference']) ? sanitize_text_field($request['reference']) : '',
+            'upload_date' => current_time('mysql'),
+            'file_path' => $file_path,
+            'file_size' => $file_size
+        );
+        
+        $item_id = $this->db_manager->insert_item($data);
+        
+        if (!$item_id) {
+            // Delete file if database insert fails
+            $this->file_manager->delete_file($file_path);
+            
+            return new WP_Error(
+                'qdr_tss_db_failed',
+                __('Failed to save file information.', 'qdr-temporary-shared-storage'),
+                array('status' => 500)
+            );
+        }
+        
+        $download_url = site_url('shared-file-download/' . $media_id);
+        $shortcode = '[qdr_tss_download id="' . $media_id . '"]';
+        
+        return new WP_REST_Response(array(
+            'media_id' => $media_id,
+            'permalink' => $download_url,
+            'shortcode' => $shortcode
+        ), 201);
+    }
+}

+ 73 - 0
qdr-temporary-shared-storage/uninstall.php

@@ -0,0 +1,73 @@
+<?php
+/**
+ * Fired when the plugin is uninstalled.
+ *
+ * @package    QDR_Temporary_Shared_Storage
+ * @since      1.0.0
+ */
+
+// If uninstall not called from WordPress, then exit.
+if (!defined('WP_UNINSTALL_PLUGIN')) {
+    exit;
+}
+
+// Define constants if not already defined
+if (!defined('QDR_TSS_PLUGIN_TABLE')) {
+    define('QDR_TSS_PLUGIN_TABLE', 'qdr_temporary_shared_storage');
+}
+
+if (!defined('QDR_TSS_UPLOADS_DIR')) {
+    $uploads_dir = wp_upload_dir();
+    define('QDR_TSS_UPLOADS_DIR', $uploads_dir['basedir'] . '/qdr-shared-storage/');
+}
+
+// Delete plugin settings
+delete_option('qdr_tss_settings');
+delete_option('qdr_tss_version');
+
+// Drop custom database table
+global $wpdb;
+$table_name = $wpdb->prefix . QDR_TSS_PLUGIN_TABLE;
+$wpdb->query("DROP TABLE IF EXISTS $table_name");
+
+// Delete all files in upload directory
+function qdr_tss_delete_directory($dir) {
+    if (!file_exists($dir)) {
+        return true;
+    }
+    
+    if (!is_dir($dir)) {
+        return unlink($dir);
+    }
+    
+    foreach (scandir($dir) as $item) {
+        if ($item == '.' || $item == '..') {
+            continue;
+        }
+        
+        if (!qdr_tss_delete_directory($dir . DIRECTORY_SEPARATOR . $item)) {
+            return false;
+        }
+    }
+    
+    return rmdir($dir);
+}
+
+// Delete upload directory
+qdr_tss_delete_directory(QDR_TSS_UPLOADS_DIR);
+
+// Delete download page
+$download_page = get_page_by_path('shared-file-download');
+if ($download_page) {
+    wp_delete_post($download_page->ID, true);
+}
+
+// Clear scheduled tasks
+$timestamp = wp_next_scheduled('qdr_tss_purge_expired_files');
+if ($timestamp) {
+    wp_unschedule_event($timestamp, 'qdr_tss_purge_expired_files');
+}
+
+// Clear any transients
+$wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_qdr_tss_%'");
+$wpdb->query("DELETE FROM $wpdb->options WHERE option_name LIKE '_transient_timeout_qdr_tss_%'");

Неке датотеке нису приказане због велике количине промена