|
@@ -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.
|