diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml
index 1130910..5118fd3 100644
--- a/.gitea/workflows/release.yml
+++ b/.gitea/workflows/release.yml
@@ -66,8 +66,44 @@ jobs:
# Copy files to the temp directory (excluding git and other unnecessary files)
cp -r * /tmp/wp-digital-download/ 2>/dev/null || true
- # Exclude .git, .gitea, and .playwright-mcp directories
- rm -rf /tmp/wp-digital-download/.git /tmp/wp-digital-download/.gitea /tmp/wp-digital-download/.playwright-mcp 2>/dev/null || true
+ # Exclude development and sensitive files from plugin distribution
+ # Remove version control and CI/CD files
+ rm -rf /tmp/wp-digital-download/.git 2>/dev/null || true
+ rm -rf /tmp/wp-digital-download/.gitea 2>/dev/null || true
+ rm -rf /tmp/wp-digital-download/.github 2>/dev/null || true
+ rm -f /tmp/wp-digital-download/.gitignore 2>/dev/null || true
+
+ # Remove testing and development files
+ rm -rf /tmp/wp-digital-download/.playwright-mcp 2>/dev/null || true
+ rm -rf /tmp/wp-digital-download/tests 2>/dev/null || true
+ rm -rf /tmp/wp-digital-download/test 2>/dev/null || true
+ rm -rf /tmp/wp-digital-download/node_modules 2>/dev/null || true
+
+ # Remove documentation and config files that might contain sensitive data
+ rm -f /tmp/wp-digital-download/CLAUDE.md 2>/dev/null || true
+ rm -f /tmp/wp-digital-download/README.md 2>/dev/null || true
+
+ # Remove ALL JSON files (package.json, composer.json, and any test configs)
+ find /tmp/wp-digital-download -type f -name "*.json" -delete 2>/dev/null || true
+
+ # Remove any lock files
+ rm -f /tmp/wp-digital-download/package-lock.json 2>/dev/null || true
+ rm -f /tmp/wp-digital-download/composer.lock 2>/dev/null || true
+
+ # Remove any .env files that might contain credentials
+ find /tmp/wp-digital-download -type f -name ".env*" -delete 2>/dev/null || true
+
+ # Remove any backup or temporary files
+ find /tmp/wp-digital-download -type f -name "*~" -delete 2>/dev/null || true
+ find /tmp/wp-digital-download -type f -name "*.bak" -delete 2>/dev/null || true
+ find /tmp/wp-digital-download -type f -name "*.tmp" -delete 2>/dev/null || true
+
+ # Remove any log files
+ find /tmp/wp-digital-download -type f -name "*.log" -delete 2>/dev/null || true
+
+ # List what's being included (for verification)
+ echo "Files being included in the release:"
+ ls -la /tmp/wp-digital-download/
# Create the ZIP file with the proper structure
cd /tmp
diff --git a/.gitea/workflows/update-version.yml b/.gitea/workflows/update-version.yml
index 2e22d2e..37d05d3 100644
--- a/.gitea/workflows/update-version.yml
+++ b/.gitea/workflows/update-version.yml
@@ -42,7 +42,23 @@ jobs:
- name: Create plugin zip
run: |
mkdir -p /tmp/wp-digital-download
- rsync -av --exclude=".git" --exclude=".gitea" --exclude=".playwright-mcp" --exclude="build" . /tmp/wp-digital-download/
+ rsync -av \
+ --exclude=".git" \
+ --exclude=".gitea" \
+ --exclude=".gitignore" \
+ --exclude=".playwright-mcp" \
+ --exclude="build" \
+ --exclude="node_modules" \
+ --exclude="tests" \
+ --exclude="test" \
+ --exclude="CLAUDE.md" \
+ --exclude="*.json" \
+ --exclude="*.log" \
+ --exclude=".env*" \
+ --exclude="*.bak" \
+ --exclude="*.tmp" \
+ --exclude="*~" \
+ . /tmp/wp-digital-download/
cd /tmp
zip -r $GITEA_WORK_DIR/wp-digital-download.zip wp-digital-download
diff --git a/.gitignore b/.gitignore
index 1fe5e52..ef1af17 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,12 +11,27 @@ wp-content/wp-cache-config.php
wp-content/cache/
wp-content/backups/
-# Plugin specific
+# Development files (excluded from plugin distribution)
node_modules/
+tests/
+test/
+CLAUDE.md
*.log
.DS_Store
Thumbs.db
+# Package and config files (may contain test data)
+package.json
+package-lock.json
+composer.json
+composer.lock
+*.json
+
+# Environment files
+.env
+.env.*
+*.env
+
# IDE files
.vscode/
.idea/
diff --git a/clean-sensitive-data.sh b/clean-sensitive-data.sh
new file mode 100755
index 0000000..4e4a65b
--- /dev/null
+++ b/clean-sensitive-data.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+
+# Script to remove sensitive files from git history
+# WARNING: This will rewrite git history!
+
+echo "WARNING: This script will rewrite git history to remove sensitive files."
+echo "Make sure you have a backup of your repository before proceeding."
+echo ""
+read -p "Do you want to continue? (y/N): " -n 1 -r
+echo ""
+
+if [[ ! $REPLY =~ ^[Yy]$ ]]; then
+ echo "Aborted."
+ exit 1
+fi
+
+echo "Removing sensitive files from git history..."
+
+# Files to remove from history
+FILES_TO_REMOVE=(
+ "CLAUDE.md"
+ "package.json"
+ "package-lock.json"
+ "composer.json"
+ "composer.lock"
+ ".playwright-mcp/*"
+ "tests/*"
+ "*.json"
+ ".env"
+ ".env.*"
+)
+
+# Remove each file from git history
+for file in "${FILES_TO_REMOVE[@]}"; do
+ echo "Removing $file from history..."
+ git filter-branch --force --index-filter \
+ "git rm -rf --cached --ignore-unmatch $file" \
+ --prune-empty --tag-name-filter cat -- --all 2>/dev/null || true
+done
+
+echo ""
+echo "Cleaning up..."
+
+# Clean up refs
+git for-each-ref --format="%(refname)" refs/original/ | xargs -n 1 git update-ref -d
+
+# Expire reflog
+git reflog expire --expire=now --all
+
+# Garbage collect
+git gc --prune=now --aggressive
+
+echo ""
+echo "Done! Sensitive files have been removed from git history."
+echo ""
+echo "IMPORTANT NEXT STEPS:"
+echo "1. Review the changes with: git log --oneline"
+echo "2. Force push to remote: git push --force --all"
+echo "3. Tell all collaborators to re-clone the repository"
+echo "4. Update any CI/CD secrets that may have been exposed"
+echo ""
+echo "The following files are now in .gitignore and won't be committed again:"
+cat .gitignore | grep -E "CLAUDE|json|.env|playwright|test" | head -10
\ No newline at end of file
diff --git a/docs/developer-integration-guide.md b/docs/developer-integration-guide.md
index 2f1e439..99a4c31 100644
--- a/docs/developer-integration-guide.md
+++ b/docs/developer-integration-guide.md
@@ -8,7 +8,33 @@ This guide shows developers how to integrate their WordPress plugins with the WP
Download `wpdd-plugin-updater.php` from your product page and include it in your plugin.
-### 2. Basic Integration
+### 2. Minimal Integration
+
+For the simplest integration with automatic license management:
+
+```php
+ true)
+ );
+}
+```
+
+This creates a license settings page under Settings → My Plugin License.
+
+### 3. Full Integration
```php
updater = new WPDD_Plugin_Updater(
- MY_PLUGIN_FILE,
+ MY_PLUGIN_FILE, // Define this constant to your main plugin file
$license_key,
'https://your-store.com',
- array('add_settings_page' => false) // We'll handle settings ourselves
+ array('add_settings_page' => false) // We'll handle settings ourselves
);
}
@@ -102,7 +135,7 @@ class My_Plugin_Settings {
public function render_license_section() {
$license_key = get_option('my_plugin_license_key', '');
- $is_valid = $this->updater->validate_license();
+ $is_valid = $this->updater ? $this->updater->validate_license() : false;
?>
License Settings
@@ -150,6 +183,11 @@ class My_Premium_Feature {
private $updater;
public function __construct() {
+ // Include updater if not already loaded
+ if (!class_exists('WPDD_Plugin_Updater')) {
+ require_once plugin_dir_path(__FILE__) . 'includes/wpdd-plugin-updater.php';
+ }
+
$license_key = get_option('my_plugin_license_key', '');
$this->updater = new WPDD_Plugin_Updater(
@@ -167,7 +205,8 @@ class My_Premium_Feature {
}
private function is_license_valid() {
- return $this->updater->validate_license();
+ // Check if updater exists and validate license
+ return $this->updater ? $this->updater->validate_license() : false;
}
private function enable_premium_features() {
@@ -195,14 +234,20 @@ class My_Premium_Feature {
### WPDD_Plugin_Updater Class
+#### Requirements
+
+- **WordPress**: 5.0 or higher
+- **PHP**: 7.0 or higher
+- **Plugin Version Header**: Your main plugin file must include a Version header
+
#### Constructor Parameters
```php
new WPDD_Plugin_Updater($plugin_file, $license_key, $update_server, $args);
```
-- **$plugin_file** (string) - Full path to your main plugin file
-- **$license_key** (string) - The user's license key
+- **$plugin_file** (string) - Full path to your main plugin file (typically `__FILE__`)
+- **$license_key** (string) - The user's license key (can be empty string)
- **$update_server** (string) - URL to your store (e.g., 'https://your-store.com')
- **$args** (array) - Optional arguments:
- `add_settings_page` (bool) - Auto-create license settings page (default: false)
@@ -352,10 +397,18 @@ Then visit: `wp-admin/plugins.php?force_update_check=1`
Always handle API failures gracefully:
```php
+// Check if updater was initialized properly
+if (!$this->updater) {
+ // Handle initialization failure
+ error_log('WPDD Updater failed to initialize');
+ return;
+}
+
$result = $updater->validate_license();
if ($result === false) {
// Network error or server down - allow functionality to continue
// but maybe show a notice
+ error_log('License validation failed - network or server issue');
}
```
@@ -388,13 +441,50 @@ if ($this->is_license_valid()) {
}
```
+## What's New in Version 1.1.0
+
+The updated `wpdd-plugin-updater.php` includes several improvements:
+
+### Enhanced Error Handling
+- Better HTTP request error handling with status code checking
+- JSON validation to prevent parsing errors
+- Improved timeout handling (30 seconds default)
+- More detailed error logging for debugging
+
+### Improved Compatibility
+- Better API response structure handling
+- Flexible URL checking for package downloads
+- Support for different server configurations
+- Fallback values for missing data fields
+
+### Security Enhancements
+- SSL verification enabled by default
+- Proper input validation in constructor
+- Safe handling of network failures
+
+### Performance Improvements
+- 12-hour caching for update checks
+- Optimized HTTP requests with proper headers
+- Reduced unnecessary API calls
+
## Troubleshooting
### Common Issues
-1. **Updates not showing:** Check that the plugin slug matches the product slug in your store
-2. **License validation fails:** Ensure the update server URL is correct and accessible
-3. **Download fails:** Verify the license is activated and not expired
+1. **Updates not showing:**
+ - Check that the plugin slug matches the product slug in your store
+ - Verify the plugin file has a proper Version header
+ - Clear update transients using the force update check method
+
+2. **License validation fails:**
+ - Ensure the update server URL is correct and accessible
+ - Check that the license key is properly formatted
+ - Verify SSL certificates are valid on your server
+
+3. **Download fails:**
+ - Verify the license is activated and not expired
+ - Check activation limits haven't been exceeded
+ - Ensure the package file exists on the server
### Debug Mode
diff --git a/includes/wpdd-plugin-updater.php b/includes/wpdd-plugin-updater.php
index 44c88f0..1fbb3ce 100644
--- a/includes/wpdd-plugin-updater.php
+++ b/includes/wpdd-plugin-updater.php
@@ -5,7 +5,21 @@
* Include this file in your WordPress plugin to enable automatic updates
* and license validation through the WP Digital Download licensing system.
*
- * @version 1.0.0
+ * Usage example:
+ *
+ * // Initialize the updater
+ * if (!class_exists('WPDD_Plugin_Updater')) {
+ * require_once 'path/to/wpdd-plugin-updater.php';
+ * }
+ *
+ * $updater = new WPDD_Plugin_Updater(
+ * __FILE__, // Main plugin file path
+ * get_option('my_plugin_license_key', ''), // License key from options
+ * 'https://your-site.com', // Update server URL
+ * array('add_settings_page' => true) // Optional: Add license settings page
+ * );
+ *
+ * @version 1.1.0
* @author WP Digital Download
*/
@@ -33,9 +47,20 @@ class WPDD_Plugin_Updater {
* @param array $args Additional arguments
*/
public function __construct($plugin_file, $license_key, $update_server, $args = array()) {
+ // Validate required parameters
+ if (empty($plugin_file) || !file_exists($plugin_file)) {
+ error_log('WPDD Updater: Invalid plugin file path provided');
+ return;
+ }
+
+ if (empty($update_server) || !filter_var($update_server, FILTER_VALIDATE_URL)) {
+ error_log('WPDD Updater: Invalid update server URL provided');
+ return;
+ }
+
$this->plugin_file = $plugin_file;
$this->plugin_slug = basename($plugin_file, '.php');
- $this->license_key = $license_key;
+ $this->license_key = trim($license_key);
$this->update_server = trailingslashit($update_server);
// Get plugin version from header
@@ -43,7 +68,7 @@ class WPDD_Plugin_Updater {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$plugin_data = get_plugin_data($plugin_file);
- $this->version = $plugin_data['Version'];
+ $this->version = $plugin_data['Version'] ?? '1.0.0';
$this->transient_key = 'wpdd_update_' . $this->plugin_slug;
@@ -90,15 +115,16 @@ class WPDD_Plugin_Updater {
$update_info = $this->request_update_info();
if ($update_info && isset($update_info['update_available']) && $update_info['update_available']) {
+ // API returns proper structure - use it directly
$plugin_data = array(
- 'slug' => $this->plugin_slug,
- 'plugin' => $this->plugin_file,
- 'new_version' => $update_info['version'],
- 'url' => $update_info['url'],
- 'package' => $update_info['package'],
- 'tested' => $update_info['tested'],
- 'requires' => $update_info['requires'],
- 'requires_php' => $update_info['requires_php'],
+ 'slug' => $update_info['slug'] ?? $this->plugin_slug,
+ 'plugin' => $update_info['plugin'] ?? $this->plugin_file,
+ 'new_version' => $update_info['version'] ?? $update_info['new_version'],
+ 'url' => $update_info['url'] ?? '',
+ 'package' => $update_info['package'] ?? $update_info['download_url'],
+ 'tested' => $update_info['tested'] ?? get_bloginfo('version'),
+ 'requires' => $update_info['requires'] ?? '5.0',
+ 'requires_php' => $update_info['requires_php'] ?? '7.0',
'compatibility' => new stdClass()
);
@@ -133,19 +159,25 @@ class WPDD_Plugin_Updater {
return $false;
}
+ // Extract product name from plugin file if available
+ if (!function_exists('get_plugin_data')) {
+ require_once ABSPATH . 'wp-admin/includes/plugin.php';
+ }
+ $plugin_data = get_plugin_data($this->plugin_file);
+
return (object) array(
'slug' => $this->plugin_slug,
- 'name' => $update_info['name'] ?? $this->plugin_slug,
- 'version' => $update_info['version'] ?? $this->version,
- 'author' => $update_info['author'] ?? '',
- 'homepage' => $update_info['url'] ?? '',
+ 'name' => $plugin_data['Name'] ?? $this->plugin_slug,
+ 'version' => $update_info['version'] ?? $update_info['new_version'] ?? $this->version,
+ 'author' => $plugin_data['Author'] ?? $update_info['author'] ?? '',
+ 'homepage' => $plugin_data['PluginURI'] ?? $update_info['url'] ?? '',
'requires' => $update_info['requires'] ?? '5.0',
'tested' => $update_info['tested'] ?? get_bloginfo('version'),
'requires_php' => $update_info['requires_php'] ?? '7.0',
- 'download_link' => $update_info['package'] ?? '',
+ 'download_link' => $update_info['package'] ?? $update_info['download_url'] ?? '',
'sections' => array(
- 'changelog' => $update_info['changelog'] ?? '',
- 'description' => $update_info['description'] ?? ''
+ 'changelog' => $update_info['changelog'] ?? $update_info['release_notes'] ?? '',
+ 'description' => $plugin_data['Description'] ?? $update_info['description'] ?? ''
),
'banners' => array(),
'icons' => array()
@@ -157,7 +189,17 @@ class WPDD_Plugin_Updater {
*/
public function maybe_download_package($reply, $package, $upgrader) {
// Check if this is our plugin's package
- if (strpos($package, $this->update_server) === false || strpos($package, $this->plugin_slug) === false) {
+ // More flexible URL checking - handle different URL structures
+ $server_host = parse_url($this->update_server, PHP_URL_HOST);
+ $package_host = parse_url($package, PHP_URL_HOST);
+
+ if ($server_host !== $package_host && strpos($package, $this->plugin_slug) === false) {
+ return $reply;
+ }
+
+ // Also check if this is a WPDD download URL pattern
+ if (strpos($package, 'wp-json/wpdd/v1/download-update') === false &&
+ strpos($package, $this->update_server) === false) {
return $reply;
}
@@ -182,10 +224,15 @@ class WPDD_Plugin_Updater {
), $url);
$response = wp_remote_get($url, array(
- 'timeout' => 15,
+ 'timeout' => 30,
+ 'redirection' => 3,
+ 'httpversion' => '1.1',
+ 'user-agent' => 'WPDD-Updater/' . $this->version . '; ' . home_url(),
'headers' => array(
- 'User-Agent' => 'WPDD-Updater/' . $this->version . '; ' . home_url()
- )
+ 'Accept' => 'application/json',
+ 'Content-Type' => 'application/json'
+ ),
+ 'sslverify' => true
));
if (is_wp_error($response)) {
@@ -193,9 +240,25 @@ class WPDD_Plugin_Updater {
return false;
}
+ $http_code = wp_remote_retrieve_response_code($response);
+ if ($http_code !== 200) {
+ error_log('WPDD Updater: HTTP error ' . $http_code . ' when checking for updates');
+ return false;
+ }
+
$body = wp_remote_retrieve_body($response);
+ if (empty($body)) {
+ error_log('WPDD Updater: Empty response body when checking for updates');
+ return false;
+ }
+
$data = json_decode($body, true);
+ if (json_last_error() !== JSON_ERROR_NONE) {
+ error_log('WPDD Updater: Invalid JSON response: ' . json_last_error_msg());
+ return false;
+ }
+
if (!$data || !isset($data['success'])) {
error_log('WPDD Updater: Invalid response from update server');
return false;
@@ -208,6 +271,7 @@ class WPDD_Plugin_Updater {
return false;
}
+ // Return the data array directly - API already provides proper structure
return $data;
}
@@ -222,15 +286,19 @@ class WPDD_Plugin_Updater {
$url = $this->update_server . 'wp-json/wpdd/v1/validate-license';
$response = wp_remote_post($url, array(
- 'timeout' => 15,
+ 'timeout' => 30,
+ 'redirection' => 3,
+ 'httpversion' => '1.1',
'body' => array(
'license_key' => $this->license_key,
'product_slug' => $this->plugin_slug,
'site_url' => home_url()
),
'headers' => array(
- 'User-Agent' => 'WPDD-Updater/' . $this->version . '; ' . home_url()
- )
+ 'User-Agent' => 'WPDD-Updater/' . $this->version . '; ' . home_url(),
+ 'Accept' => 'application/json'
+ ),
+ 'sslverify' => true
));
if (is_wp_error($response)) {
@@ -261,7 +329,9 @@ class WPDD_Plugin_Updater {
$url = $this->update_server . 'wp-json/wpdd/v1/activate-license';
$response = wp_remote_post($url, array(
- 'timeout' => 15,
+ 'timeout' => 30,
+ 'redirection' => 3,
+ 'httpversion' => '1.1',
'body' => array(
'license_key' => $this->license_key,
'site_url' => home_url(),
@@ -270,8 +340,10 @@ class WPDD_Plugin_Updater {
'php_version' => PHP_VERSION
),
'headers' => array(
- 'User-Agent' => 'WPDD-Updater/' . $this->version . '; ' . home_url()
- )
+ 'User-Agent' => 'WPDD-Updater/' . $this->version . '; ' . home_url(),
+ 'Accept' => 'application/json'
+ ),
+ 'sslverify' => true
));
if (is_wp_error($response)) {
@@ -308,14 +380,18 @@ class WPDD_Plugin_Updater {
$url = $this->update_server . 'wp-json/wpdd/v1/deactivate-license';
$response = wp_remote_post($url, array(
- 'timeout' => 15,
+ 'timeout' => 30,
+ 'redirection' => 3,
+ 'httpversion' => '1.1',
'body' => array(
'license_key' => $this->license_key,
'site_url' => home_url()
),
'headers' => array(
- 'User-Agent' => 'WPDD-Updater/' . $this->version . '; ' . home_url()
- )
+ 'User-Agent' => 'WPDD-Updater/' . $this->version . '; ' . home_url(),
+ 'Accept' => 'application/json'
+ ),
+ 'sslverify' => true
));
if (is_wp_error($response)) {