integration-guide.md 54 KB

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
  2. Mobile Applications
  3. Desktop Applications
  4. CRM Systems
  5. Document Management Systems
  6. Email Systems
  7. Workflow Automation

Web Applications

Basic HTML Form Integration

You can create a simple HTML form that interacts with the plugin's REST API.

<!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:

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:

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

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

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

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
/**
 * 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
/**
 * 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:

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