mirror of
https://github.com/SilverAssist/wp-github-updater.git
synced 2025-11-20 12:01:33 +08:00
Compare commits
29 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
df2d661825 | ||
|
|
6ba7cdea18 | ||
|
|
d3a75e9dd5 | ||
|
|
0dd55d3b64 | ||
|
|
012ee32202 | ||
|
|
7c5ac1516f | ||
|
|
5f86517b71 | ||
|
|
a6ec354462 | ||
|
|
b3e84d6999 | ||
|
|
85338a97e6 | ||
|
|
811e8a6ea3 | ||
|
|
b6e0c10b4c | ||
|
|
4e064581ff | ||
|
|
7faeb7cd2f | ||
|
|
532b4ff589 | ||
|
|
0067b4d9f5 | ||
|
|
8c1c129c9e | ||
|
|
42c9116ccb | ||
|
|
6dc9280a9e | ||
|
|
f9809f3d5a | ||
|
|
9eb29aa047 | ||
|
|
8934970acd | ||
|
|
1caafb51df | ||
|
|
1fb5c2cdab | ||
|
|
8c7402be31 | ||
|
|
fd5a2b905f | ||
|
|
88d5c01009 | ||
|
|
d7bf5d9124 | ||
|
|
8501855e8e |
26 changed files with 3433 additions and 92 deletions
93
.github/copilot-instructions.md
vendored
93
.github/copilot-instructions.md
vendored
|
|
@ -1,12 +1,35 @@
|
|||
# Copilot Instructions for WP GitHub Updater Package
|
||||
|
||||
## 📋 Documentation Policy
|
||||
|
||||
**CRITICAL: ALL documentation must be consolidated in these three files ONLY:**
|
||||
|
||||
1. **README.md** - User-facing documentation, installation, usage examples, API reference
|
||||
2. **CHANGELOG.md** - Version history, changes, bug fixes, new features
|
||||
3. **.github/copilot-instructions.md** - This file, development guidelines and architecture
|
||||
|
||||
**🚫 NEVER create separate documentation files:**
|
||||
- ❌ Do NOT create files like `docs/GUIDE.md`, `docs/TESTING.md`, `TROUBLESHOOTING.md`, etc.
|
||||
- ❌ Do NOT create a `docs/` directory
|
||||
- ❌ Do NOT split documentation into multiple MD files
|
||||
|
||||
**✅ Instead:**
|
||||
- Add user documentation to appropriate sections in README.md
|
||||
- Add development/architecture notes to this copilot-instructions.md file
|
||||
- Add version changes to CHANGELOG.md
|
||||
- Use inline code comments for complex logic explanation
|
||||
|
||||
**Rationale:** Centralized documentation is easier to maintain, search, and keep in sync with code changes.
|
||||
|
||||
---
|
||||
|
||||
## Architecture Overview
|
||||
|
||||
This is a **reusable Composer package** that provides WordPress plugin update functionality from GitHub releases. The package is designed to be integrated into various WordPress plugins, not as a standalone plugin.
|
||||
|
||||
## Core Technologies
|
||||
|
||||
- **PHP 8.0+**: Modern PHP with strict typing, union types, and nullable parameters
|
||||
- **PHP 8.2+**: Modern PHP with strict typing, union types, and nullable parameters
|
||||
- **WordPress 6.0+**: WordPress update system integration via hooks and filters
|
||||
- **PSR-4 Autoloading**: Namespace-based class loading for better organization
|
||||
- **GitHub API v3**: REST API integration for release management
|
||||
|
|
@ -44,6 +67,7 @@ wp-github-updater/
|
|||
- Cache duration and WordPress requirements
|
||||
- AJAX endpoints and nonce configuration
|
||||
- Plugin data parsing from WordPress headers
|
||||
- **Custom temporary directory configuration** (v1.1.3+)
|
||||
|
||||
### Updater Class
|
||||
**Purpose**: Core WordPress integration and GitHub API communication
|
||||
|
|
@ -52,8 +76,11 @@ wp-github-updater/
|
|||
- GitHub API communication with caching
|
||||
- Plugin update checking and information display
|
||||
- Download error handling and recovery
|
||||
- AJAX endpoints for manual update checks
|
||||
- AJAX endpoints for manual update checks and notice dismissal
|
||||
- Markdown-to-HTML conversion for changelogs
|
||||
- **Enhanced temporary file management with PCLZIP error resolution** (v1.1.3+)
|
||||
- **WordPress admin notices for manual update checks** (v1.1.3+)
|
||||
- **Dismissible admin notices with AJAX handling** (v1.1.4+)
|
||||
|
||||
### Security
|
||||
- **Nonce verification** for AJAX requests
|
||||
|
|
@ -90,6 +117,34 @@ class UpdaterConfig {
|
|||
\__("Update available", "wp-github-updater");
|
||||
```
|
||||
|
||||
### Temporary File Management Strategy
|
||||
**New in v1.1.3**: Enhanced handling to resolve `PCLZIP_ERR_MISSING_FILE (-4)` errors
|
||||
|
||||
#### Multi-Tier Fallback System
|
||||
The package implements a robust 6-tier fallback system for temporary file creation:
|
||||
|
||||
1. **Custom temporary directory** - User-specified via `custom_temp_dir` option
|
||||
2. **WordPress uploads directory** - Most reliable, within web root
|
||||
3. **WP_CONTENT_DIR/temp** - Auto-created if needed
|
||||
4. **WP_TEMP_DIR** - If defined in wp-config.php
|
||||
5. **System temporary directory** - OS default /tmp or equivalent
|
||||
6. **Manual file creation** - Last resort fallback
|
||||
|
||||
#### Configuration Options
|
||||
```php
|
||||
$config = new UpdaterConfig($pluginFile, $githubRepo, [
|
||||
"custom_temp_dir" => WP_CONTENT_DIR . "/temp", // Custom directory
|
||||
// or
|
||||
"custom_temp_dir" => wp_upload_dir()["basedir"] . "/temp", // Uploads subdirectory
|
||||
]);
|
||||
```
|
||||
|
||||
#### WordPress Configuration Alternative
|
||||
```php
|
||||
// In wp-config.php
|
||||
define('WP_TEMP_DIR', ABSPATH . 'wp-content/temp');
|
||||
```
|
||||
|
||||
### Coding Standards
|
||||
|
||||
- **WordPress Coding Standards**: Full compliance with WordPress PHP standards
|
||||
|
|
@ -137,13 +192,41 @@ class Updater {
|
|||
}
|
||||
|
||||
public function checkForUpdate($transient) {
|
||||
// WordPress update check integration
|
||||
// WordPress update check integration using isUpdateAvailable() method
|
||||
}
|
||||
|
||||
public function pluginInfo($result, string $action, object $args) {
|
||||
// Plugin information display
|
||||
}
|
||||
|
||||
public function manualVersionCheck(): void {
|
||||
// AJAX handler for manual version checks with admin notice creation
|
||||
}
|
||||
|
||||
public function dismissUpdateNotice(): void {
|
||||
// AJAX handler for dismissing admin notices (v1.1.4+)
|
||||
}
|
||||
|
||||
public function showUpdateNotice(): void {
|
||||
// WordPress admin notice display for available updates (v1.1.4+)
|
||||
}
|
||||
|
||||
public function maybeFixDownload($result, string $package, object $upgrader, array $hook_extra) {
|
||||
// Enhanced download handling with PCLZIP error resolution (v1.1.3+)
|
||||
}
|
||||
|
||||
private function createSecureTempFile(string $package) {
|
||||
// Multi-tier temporary file creation with fallback strategies (v1.1.3+)
|
||||
}
|
||||
|
||||
public function isUpdateAvailable(): bool {
|
||||
// Centralized update availability check (refactored in v1.1.4+)
|
||||
}
|
||||
|
||||
public function getLatestVersion(): string|false {
|
||||
// Public API method for external access (v1.1.2+)
|
||||
}
|
||||
|
||||
// ... other methods
|
||||
}
|
||||
```
|
||||
|
|
@ -277,6 +360,7 @@ $config = new UpdaterConfig([
|
|||
"github_username" => "username",
|
||||
"github_repo" => "repository",
|
||||
"text_domain" => "my-plugin-domain", // Consumer's text domain
|
||||
"custom_temp_dir" => WP_CONTENT_DIR . "/temp", // Optional: Custom temp directory (v1.1.3+)
|
||||
// ... other options
|
||||
]);
|
||||
|
||||
|
|
@ -288,6 +372,9 @@ $updater = new Updater($config);
|
|||
- Provide fallback download mechanisms for GitHub API failures
|
||||
- Cache GitHub API responses to avoid rate limiting
|
||||
- Handle PCLZIP errors with alternative download methods
|
||||
- **PCLZIP_ERR_MISSING_FILE Resolution** (v1.1.3+): Multi-tier temporary file creation to avoid /tmp permission issues
|
||||
- Cache GitHub API responses to avoid rate limiting
|
||||
- Handle PCLZIP errors with alternative download methods
|
||||
|
||||
### Version Management
|
||||
- Follow semantic versioning (MAJOR.MINOR.PATCH)
|
||||
|
|
|
|||
45
.github/dependabot.yml
vendored
Normal file
45
.github/dependabot.yml
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
version: 2
|
||||
updates:
|
||||
# Composer dependency updates
|
||||
- package-ecosystem: "composer"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "09:00"
|
||||
timezone: "America/Mexico_City"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "composer"
|
||||
- "automated"
|
||||
open-pull-requests-limit: 10
|
||||
groups:
|
||||
composer-updates:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
|
||||
# GitHub Actions dependency updates
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: "weekly"
|
||||
day: "monday"
|
||||
time: "09:00"
|
||||
timezone: "America/Mexico_City"
|
||||
labels:
|
||||
- "dependencies"
|
||||
- "github-actions"
|
||||
- "automated"
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
# Group all GitHub Actions updates together
|
||||
groups:
|
||||
github-actions-updates:
|
||||
patterns:
|
||||
- "*"
|
||||
update-types:
|
||||
- "minor"
|
||||
- "patch"
|
||||
44
.github/workflows/create-release.yml
vendored
44
.github/workflows/create-release.yml
vendored
|
|
@ -13,24 +13,48 @@ on:
|
|||
|
||||
jobs:
|
||||
build-and-release:
|
||||
permissions:
|
||||
contents: write
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
|
||||
|
||||
- name: Setup PHP
|
||||
uses: shivammathur/setup-php@v2
|
||||
uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2.35.5
|
||||
with:
|
||||
php-version: "8.1"
|
||||
extensions: mbstring, xml, ctype, json, tokenizer
|
||||
php-version: "8.2"
|
||||
extensions: mbstring, xml, ctype, json, tokenizer, mysqli
|
||||
coverage: none
|
||||
|
||||
- name: Setup MySQL
|
||||
run: |
|
||||
sudo systemctl start mysql.service
|
||||
mysql -e "CREATE DATABASE IF NOT EXISTS wordpress_test;" -uroot -proot
|
||||
mysql -e "CREATE USER IF NOT EXISTS 'wp_test'@'localhost' IDENTIFIED BY 'wp_test';" -uroot -proot
|
||||
mysql -e "GRANT ALL PRIVILEGES ON wordpress_test.* TO 'wp_test'@'localhost';" -uroot -proot
|
||||
mysql -e "FLUSH PRIVILEGES;" -uroot -proot
|
||||
echo "✅ MySQL database configured"
|
||||
|
||||
- name: Install Subversion (required for WordPress Test Suite)
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y subversion
|
||||
echo "✅ Subversion installed"
|
||||
|
||||
- name: Install Composer dependencies
|
||||
run: |
|
||||
composer install --optimize-autoloader --no-interaction
|
||||
echo "✅ Composer dependencies installed successfully (including dev dependencies for testing)"
|
||||
|
||||
- name: Install WordPress Test Suite
|
||||
run: |
|
||||
echo "📦 Installing WordPress Test Suite..."
|
||||
chmod +x scripts/install-wp-tests.sh
|
||||
bash scripts/install-wp-tests.sh wordpress_test wp_test wp_test localhost latest
|
||||
echo "✅ WordPress Test Suite installed"
|
||||
|
||||
- name: Extract version from tag or input
|
||||
id: version
|
||||
run: |
|
||||
|
|
@ -76,14 +100,14 @@ jobs:
|
|||
|
||||
- name: Run tests
|
||||
run: |
|
||||
echo "🧪 Running PHPUnit tests..."
|
||||
echo "🧪 Running PHPUnit tests with WordPress Test Suite..."
|
||||
echo "Working directory: $(pwd)"
|
||||
echo "Checking for phpunit.xml:"
|
||||
ls -la phpunit.xml || echo "phpunit.xml not found"
|
||||
echo "Checking tests directory:"
|
||||
ls -la tests/
|
||||
./vendor/bin/phpunit --configuration=phpunit.xml
|
||||
echo "✅ All tests passed"
|
||||
echo "✅ All tests passed (including WordPress integration tests)"
|
||||
|
||||
- name: Run code quality checks
|
||||
run: |
|
||||
|
|
@ -138,7 +162,7 @@ jobs:
|
|||
- **Version**: $VERSION
|
||||
- **Namespace**: SilverAssist\\WpGithubUpdater
|
||||
- **License**: PolyForm Noncommercial 1.0.0
|
||||
- **PHP Version**: 8.0+
|
||||
- **PHP Version**: 8.2+
|
||||
- **WordPress Version**: 6.0+
|
||||
|
||||
## 🚀 Installation via Composer
|
||||
|
|
@ -177,7 +201,7 @@ jobs:
|
|||
- **API Docs**: Comprehensive PHPDoc documentation
|
||||
|
||||
## 🔧 Requirements
|
||||
- PHP 8.0 or higher
|
||||
- PHP 8.2 or higher
|
||||
- WordPress 6.0 or higher
|
||||
- Composer for package management
|
||||
- GitHub repository with releases for updates
|
||||
|
|
@ -217,7 +241,7 @@ jobs:
|
|||
\`\`\`
|
||||
|
||||
## Requirements
|
||||
- PHP 8.0+
|
||||
- PHP 8.2+
|
||||
- WordPress 6.0+
|
||||
- Composer
|
||||
- GitHub repository with releases
|
||||
|
|
@ -258,7 +282,7 @@ jobs:
|
|||
EOF
|
||||
|
||||
- name: Create GitHub Release
|
||||
uses: softprops/action-gh-release@v1
|
||||
uses: softprops/action-gh-release@de2c0eb89ae2a093876385947365aca7b0e5f844 # v1
|
||||
with:
|
||||
tag_name: ${{ steps.version.outputs.tag }}
|
||||
name: "WP GitHub Updater v${{ steps.version.outputs.version }}"
|
||||
|
|
|
|||
107
CHANGELOG.md
107
CHANGELOG.md
|
|
@ -1,5 +1,112 @@
|
|||
# Changelog
|
||||
|
||||
## [1.2.1] - 2025-10-11
|
||||
|
||||
### Changed
|
||||
- **PHP Version Requirement**: Lowered minimum PHP version from 8.3 to 8.2 for broader compatibility
|
||||
- **Documentation Updates**: Updated all documentation, examples, and tests to reflect PHP 8.2 requirement
|
||||
- **Workflow Updates**: Updated GitHub Actions workflow to reflect PHP 8.2 requirement
|
||||
|
||||
### Testing
|
||||
- **51 Tests Passing**: All tests verified with PHP 8.2 compatibility
|
||||
- **Test Fixtures Updated**: Updated all test fixtures and expectations for PHP 8.2
|
||||
|
||||
## [1.2.0] - 2025-10-10
|
||||
|
||||
### Changed
|
||||
- **Documentation Consolidation**: Centralized all documentation into README.md, CHANGELOG.md, and .github/copilot-instructions.md
|
||||
- **Removed Separate Documentation Files**: Eliminated `docs/` directory to maintain simpler, more maintainable documentation structure
|
||||
- **Testing Documentation**: Moved comprehensive testing guide to README.md Development section
|
||||
- **Troubleshooting Guide**: Integrated troubleshooting information directly into README.md
|
||||
|
||||
### Testing
|
||||
- **51 Tests, 130 Assertions**: Complete test suite with 100% pass rate
|
||||
- **Real GitHub API Integration Tests**: 9 tests making actual HTTP requests to production repositories
|
||||
- **Test Coverage**: Unit tests (3), Integration tests (22), WordPress tests (26)
|
||||
- **Performance Verification**: Caching performance tests confirm < 10ms for cached API calls
|
||||
|
||||
## [1.1.5] - 2025-10-10
|
||||
### Fixed
|
||||
- **PCLZIP_ERR_MISSING_FILE (-4) Resolution**: Complete rewrite of `upgrader_pre_download` filter to properly handle all download scenarios
|
||||
- **Download Filter Return Values**: Fixed critical issue where filter could return invalid types causing WordPress to fail with PCLZIP errors
|
||||
- **Better Plugin Detection**: Added robust verification to ensure filter only intercepts downloads for the correct plugin
|
||||
- **Enhanced Error Handling**: Comprehensive error messages for all failure points in the download process
|
||||
- **File Verification**: Added multiple validation checks (file size, readability, existence) before returning downloaded file to WordPress
|
||||
|
||||
### Changed
|
||||
- **Stricter Filter Logic**: `maybeFixDownload()` now returns `false` to let WordPress handle downloads that aren't for our plugin
|
||||
- **Safety Checks**: Added verification of `hook_extra` data to ensure we only process downloads for our specific plugin
|
||||
- **Improved Documentation**: Enhanced PHPDoc comments explaining critical return value requirements for WordPress compatibility
|
||||
- **Download Process**: Better handling of HTTP response codes and empty responses with descriptive error messages
|
||||
|
||||
### Technical Improvements
|
||||
- **Return Type Enforcement**: Strict enforcement of `string|WP_Error|false` return types (never `true` or other types)
|
||||
- **Multi-line Conditionals**: Improved code formatting to meet WordPress Coding Standards (120 character line limit)
|
||||
- **Defensive Programming**: Added early returns for edge cases where previous filters have already handled the download
|
||||
- **Minimum File Size Check**: Validates downloaded file is at least 100 bytes before considering it valid
|
||||
|
||||
## [1.1.4] - 2025-08-29
|
||||
### Added
|
||||
- **WordPress Admin Notices**: Integrated admin notification system that displays update availability after manual version checks
|
||||
- **Dismissible Update Notices**: Users can dismiss update notifications with built-in AJAX functionality
|
||||
- **Admin Notice Management**: New `showUpdateNotice()` method creates WordPress-compliant admin notices with proper styling
|
||||
- **AJAX Notice Dismissal**: New `dismissUpdateNotice()` AJAX handler for seamless notice management
|
||||
- **Transient-Based Notifications**: Update notices persist for the same duration as version cache (configurable via `cache_duration`)
|
||||
|
||||
### Changed
|
||||
- **Improved Manual Version Checks**: Enhanced `manualVersionCheck()` method now sets admin notices for immediate user feedback
|
||||
- **Code Refactoring**: Centralized update availability logic using `isUpdateAvailable()` method to eliminate code duplication
|
||||
- **Better WordPress Integration**: Manual version checks now properly clear WordPress update transients for immediate admin interface updates
|
||||
- **Enhanced User Experience**: Update checks provide both AJAX responses and persistent admin notifications
|
||||
|
||||
### Fixed
|
||||
- **WordPress Admin Sync**: Manual version checks now immediately reflect in WordPress admin plugins page
|
||||
- **Transient Cache Management**: Proper clearing of both plugin-specific and WordPress update caches
|
||||
- **Admin Interface Updates**: Resolved disconnect between manual checks and WordPress admin display
|
||||
|
||||
### Technical Improvements
|
||||
- **DRY Principle**: Replaced duplicate version comparison logic with centralized `isUpdateAvailable()` method calls
|
||||
- **AJAX Security**: Enhanced nonce verification and sanitization for all AJAX endpoints
|
||||
- **WordPress Standards**: All admin notices follow WordPress UI/UX guidelines with proper escaping and styling
|
||||
- **JavaScript Integration**: Inline JavaScript for notice dismissal with jQuery compatibility
|
||||
|
||||
### Documentation
|
||||
- **API Documentation**: Added comprehensive Public API Methods section to README
|
||||
- **Integration Examples**: Updated all examples to demonstrate new admin notice features
|
||||
- **Configuration Guide**: Enhanced advanced configuration examples with new capabilities
|
||||
- **Code Examples**: Programmatic version checking examples for developers
|
||||
|
||||
## [1.1.3] - 2025-08-29
|
||||
### Added
|
||||
- **Enhanced Temporary File Handling**: Implemented multiple fallback strategies for temporary file creation to resolve `PCLZIP_ERR_MISSING_FILE (-4)` errors
|
||||
- **Custom Temporary Directory Support**: New `custom_temp_dir` configuration option in UpdaterConfig for specifying alternative temporary directories
|
||||
- **Automatic Directory Creation**: The updater now attempts to create temporary directories if they don't exist
|
||||
- **Comprehensive File Verification**: Added file existence and readability checks after download to prevent installation failures
|
||||
|
||||
### Changed
|
||||
- **Improved Download Reliability**: Enhanced `maybeFixDownload()` method with better error handling and multiple fallback strategies
|
||||
- **Robust Temporary File Strategy**: Six-tier fallback system for temporary file creation:
|
||||
1. Custom temporary directory (if configured)
|
||||
2. WordPress uploads directory
|
||||
3. WP_CONTENT_DIR/temp (auto-created)
|
||||
4. WP_TEMP_DIR (if defined in wp-config.php)
|
||||
5. System temporary directory
|
||||
6. Manual file creation as last resort
|
||||
|
||||
### Fixed
|
||||
- **PCLZIP Error Resolution**: Addresses `PCLZIP_ERR_MISSING_FILE (-4)` errors caused by restrictive /tmp directory permissions
|
||||
- **File Write Verification**: Added byte-level verification to ensure complete file downloads
|
||||
- **Permission Issues**: Better handling of directory permission problems during plugin updates
|
||||
|
||||
### Documentation
|
||||
- **Integration Examples**: Added examples for handling PCLZIP errors in integration guide
|
||||
- **WordPress Configuration**: Documented wp-config.php approach for setting custom temporary directories
|
||||
- **Troubleshooting Guide**: Comprehensive examples for different temporary directory configuration strategies
|
||||
|
||||
## [1.1.2] - 2025-08-19
|
||||
### Changed
|
||||
- **API Accessibility**: Changed `getLatestVersion()` method visibility from `private` to `public` to allow external access from consuming plugins
|
||||
|
||||
## [1.1.1] - 2025-08-14
|
||||
### Changed
|
||||
- **License Migration**: Updated from GPL v2.0+ to PolyForm Noncommercial 1.0.0
|
||||
|
|
|
|||
186
README.md
186
README.md
|
|
@ -14,6 +14,9 @@ A reusable WordPress plugin updater that handles automatic updates from public G
|
|||
- 🛡️ **Security**: AJAX nonce verification and capability checks
|
||||
- 🔧 **Configurable**: Flexible configuration options
|
||||
- 📦 **Easy Integration**: Simple Composer installation
|
||||
- 📢 **Admin Notices**: WordPress admin notifications for available updates
|
||||
- 🗂️ **Enhanced File Handling**: Multi-tier temporary file creation to resolve hosting issues
|
||||
- ✅ **Manual Version Checks**: AJAX-powered manual update checking with immediate admin feedback
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -60,11 +63,13 @@ $config = new UpdaterConfig(
|
|||
'plugin_author' => 'Your Name',
|
||||
'plugin_homepage' => 'https://your-website.com',
|
||||
'requires_wordpress' => '6.0',
|
||||
'requires_php' => '8.0',
|
||||
'requires_php' => '8.3',
|
||||
'asset_pattern' => '{slug}-v{version}.zip', // GitHub release asset filename
|
||||
'cache_duration' => 12 * 3600, // 12 hours in seconds
|
||||
'ajax_action' => 'my_plugin_check_version',
|
||||
'ajax_nonce' => 'my_plugin_version_nonce'
|
||||
'ajax_nonce' => 'my_plugin_version_nonce',
|
||||
'text_domain' => 'my-plugin-textdomain', // For internationalization
|
||||
'custom_temp_dir' => WP_CONTENT_DIR . '/temp', // Custom temporary directory (v1.1.3+)
|
||||
]
|
||||
);
|
||||
|
||||
|
|
@ -80,12 +85,13 @@ $updater = new Updater($config);
|
|||
| `plugin_author` | string | From plugin header | Plugin author name |
|
||||
| `plugin_homepage` | string | GitHub repo URL | Plugin homepage URL |
|
||||
| `requires_wordpress` | string | `'6.0'` | Minimum WordPress version |
|
||||
| `requires_php` | string | `'8.0'` | Minimum PHP version |
|
||||
| `requires_php` | string | `'8.3'` | Minimum PHP version |
|
||||
| `asset_pattern` | string | `'{slug}-v{version}.zip'` | GitHub release asset filename pattern |
|
||||
| `cache_duration` | int | `43200` (12 hours) | Cache duration in seconds |
|
||||
| `ajax_action` | string | `'check_plugin_version'` | AJAX action name for manual checks |
|
||||
| `ajax_nonce` | string | `'plugin_version_check'` | AJAX nonce name |
|
||||
| `text_domain` | string | `'wp-github-updater'` | WordPress text domain for i18n **(New in 1.1.0)** |
|
||||
| `custom_temp_dir` | string\|null | `null` | Custom temporary directory path **(New in 1.1.3)** |
|
||||
|
||||
### Internationalization Support (i18n)
|
||||
|
||||
|
|
@ -153,7 +159,7 @@ Here's a complete example for a WordPress plugin:
|
|||
* Version: 1.0.0
|
||||
* Author: Your Name
|
||||
* Requires at least: 6.0
|
||||
* Requires PHP: 8.0
|
||||
* Requires PHP: 8.3
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
|
|
@ -185,19 +191,155 @@ add_action('init', function() {
|
|||
// Your plugin code here...
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### PCLZIP_ERR_MISSING_FILE (-4) Error
|
||||
|
||||
If you encounter the error "The package could not be installed. PCLZIP_ERR_MISSING_FILE (-4)", this typically indicates issues with the temporary directory. The updater includes multiple fallback strategies to resolve this.
|
||||
|
||||
#### Solution 1: Custom Temporary Directory (Recommended)
|
||||
|
||||
```php
|
||||
$config = new UpdaterConfig(
|
||||
__FILE__,
|
||||
'your-username/your-repo',
|
||||
[
|
||||
'custom_temp_dir' => WP_CONTENT_DIR . '/temp',
|
||||
// or use uploads directory:
|
||||
// 'custom_temp_dir' => wp_upload_dir()['basedir'] . '/temp',
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
#### Solution 2: WordPress Configuration
|
||||
|
||||
Add to your `wp-config.php` file (before "That's all, stop editing!"):
|
||||
|
||||
```php
|
||||
/* Set WordPress temporary directory */
|
||||
define('WP_TEMP_DIR', ABSPATH . 'wp-content/temp');
|
||||
```
|
||||
|
||||
Then create the directory with proper permissions:
|
||||
|
||||
```bash
|
||||
mkdir wp-content/temp
|
||||
chmod 755 wp-content/temp
|
||||
```
|
||||
|
||||
#### Solution 3: Plugin Activation Hook
|
||||
|
||||
Create the temporary directory when your plugin is activated:
|
||||
|
||||
```php
|
||||
register_activation_hook(__FILE__, function() {
|
||||
$temp_dir = WP_CONTENT_DIR . '/temp';
|
||||
if (!file_exists($temp_dir)) {
|
||||
wp_mkdir_p($temp_dir);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
### Multi-Tier Fallback System
|
||||
|
||||
The updater automatically tries multiple strategies for temporary file creation:
|
||||
|
||||
1. **Custom temporary directory** (if configured)
|
||||
2. **WordPress uploads directory**
|
||||
3. **WP_CONTENT_DIR/temp** (auto-created)
|
||||
4. **WP_TEMP_DIR** (if defined in wp-config.php)
|
||||
5. **System temporary directory** (/tmp)
|
||||
6. **Manual file creation** (last resort)
|
||||
|
||||
## Requirements
|
||||
|
||||
- PHP 8.0 or higher
|
||||
- PHP 8.2 or higher
|
||||
- WordPress 6.0 or higher
|
||||
- Composer for dependency management
|
||||
- Public GitHub repository with releases
|
||||
|
||||
## Development
|
||||
|
||||
### Running Tests
|
||||
### Testing
|
||||
|
||||
The package includes comprehensive testing (51 tests, 130 assertions, 100% passing):
|
||||
|
||||
**Test Coverage:**
|
||||
- **Unit Tests** (3 tests): Configuration and core functionality
|
||||
- **Integration Tests** (22 tests): Updater + Config integration, download filters, **real GitHub API** ⭐
|
||||
- **WordPress Tests** (26 tests): Hooks, filters, and mock plugin integration
|
||||
|
||||
**Running Tests:**
|
||||
|
||||
```bash
|
||||
# Install development dependencies
|
||||
composer install --dev
|
||||
|
||||
# Run all tests
|
||||
composer test
|
||||
|
||||
# Run specific test suites
|
||||
./scripts/test-runner.sh unit # Unit tests only
|
||||
./scripts/test-runner.sh integration # Integration tests (includes real GitHub API)
|
||||
./scripts/test-runner.sh wordpress # WordPress integration tests
|
||||
./scripts/test-runner.sh all # All tests
|
||||
|
||||
# Run with coverage
|
||||
vendor/bin/phpunit --coverage-text
|
||||
```
|
||||
|
||||
**Real GitHub API Testing:**
|
||||
|
||||
The integration tests include **real HTTP requests** to production GitHub repositories to verify actual API behavior:
|
||||
|
||||
- ✅ Validates actual GitHub API response structure
|
||||
- ✅ Verifies caching performance (< 10ms for cached calls)
|
||||
- ✅ Tests version comparison with real releases
|
||||
- ✅ Confirms asset pattern matching with production URLs
|
||||
|
||||
**Example: Test with Your Own Repository**
|
||||
|
||||
```php
|
||||
// tests/Integration/MyRealAPITest.php
|
||||
public function testFetchLatestVersionFromMyRepo(): void {
|
||||
$config = new UpdaterConfig([
|
||||
"plugin_file" => __FILE__,
|
||||
"github_username" => "YourUsername",
|
||||
"github_repo" => "your-repository",
|
||||
]);
|
||||
|
||||
$updater = new Updater($config);
|
||||
$version = $updater->getLatestVersion();
|
||||
|
||||
$this->assertNotFalse($version);
|
||||
$this->assertMatchesRegularExpression("/^\d+\.\d+\.\d+$/", $version);
|
||||
}
|
||||
```
|
||||
|
||||
**Test Environment Setup:**
|
||||
|
||||
The tests use WordPress Test Suite for authentic WordPress integration:
|
||||
|
||||
```bash
|
||||
# Install WordPress Test Suite (interactive)
|
||||
./scripts/test-runner.sh install
|
||||
|
||||
# Or manual installation
|
||||
./scripts/install-wp-tests.sh wordpress_test root '' localhost latest
|
||||
```
|
||||
|
||||
**PCLZIP Error Testing:**
|
||||
|
||||
For testing plugins that may experience `PCLZIP_ERR_MISSING_FILE (-4)` errors, configure a custom temporary directory:
|
||||
|
||||
```php
|
||||
$config = new UpdaterConfig(
|
||||
__FILE__,
|
||||
"your-username/your-plugin",
|
||||
[
|
||||
"custom_temp_dir" => WP_CONTENT_DIR . "/temp",
|
||||
]
|
||||
);
|
||||
```
|
||||
|
||||
### Code Standards
|
||||
|
|
@ -219,6 +361,38 @@ composer phpstan
|
|||
composer check
|
||||
```
|
||||
|
||||
## Public API Methods
|
||||
|
||||
The updater provides several public methods for programmatic access:
|
||||
|
||||
### Version Information
|
||||
|
||||
```php
|
||||
// Check if an update is available
|
||||
$hasUpdate = $updater->isUpdateAvailable(); // Returns bool
|
||||
|
||||
// Get the current plugin version
|
||||
$currentVersion = $updater->getCurrentVersion(); // Returns string
|
||||
|
||||
// Get the latest version from GitHub (with caching)
|
||||
$latestVersion = $updater->getLatestVersion(); // Returns string|false
|
||||
|
||||
// Get the GitHub repository
|
||||
$repo = $updater->getGithubRepo(); // Returns string
|
||||
```
|
||||
|
||||
### Manual Version Check
|
||||
|
||||
You can trigger a manual version check programmatically:
|
||||
|
||||
```php
|
||||
// This will clear caches and check for updates
|
||||
// If an update is available, it will set an admin notice
|
||||
$updater->manualVersionCheck();
|
||||
```
|
||||
|
||||
**Note**: The manual version check method is designed for AJAX calls and will send JSON responses. For programmatic use, prefer the individual methods above.
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Fork the repository
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "silverassist/wp-github-updater",
|
||||
"description": "A reusable WordPress plugin updater that handles automatic updates from public GitHub releases",
|
||||
"version": "1.1.1",
|
||||
"version": "1.2.1",
|
||||
"type": "library",
|
||||
"keywords": [
|
||||
"wordpress",
|
||||
|
|
@ -20,14 +20,19 @@
|
|||
}
|
||||
],
|
||||
"require": {
|
||||
"php": ">=8.0"
|
||||
"php": ">=8.2"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^10.0",
|
||||
"phpunit/phpunit": "^9.6",
|
||||
"squizlabs/php_codesniffer": "^3.7",
|
||||
"phpstan/phpstan": "^1.10",
|
||||
"wp-coding-standards/wpcs": "^3.0",
|
||||
"slevomat/coding-standard": "^8.0"
|
||||
"slevomat/coding-standard": "^8.0",
|
||||
"php-stubs/wordpress-stubs": "^6.8",
|
||||
"php-stubs/wordpress-tests-stubs": "^6.8",
|
||||
"phpcompatibility/phpcompatibility-wp": "^2.1",
|
||||
"szepeviktor/phpstan-wordpress": "^1.3",
|
||||
"yoast/phpunit-polyfills": "^2.0 || ^4.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
@ -41,6 +46,9 @@
|
|||
},
|
||||
"scripts": {
|
||||
"test": "phpunit",
|
||||
"test:unit": "phpunit --testsuite unit",
|
||||
"test:integration": "phpunit --testsuite integration",
|
||||
"test:wordpress": "phpunit --testsuite wordpress",
|
||||
"phpcs": "phpcs",
|
||||
"phpcbf": "phpcbf",
|
||||
"phpstan": "phpstan analyse src/ --level=8",
|
||||
|
|
|
|||
|
|
@ -30,11 +30,19 @@ add_action("init", function() {
|
|||
"asset_pattern" => "silver-assist-security-v{version}.zip",
|
||||
"ajax_action" => "silver_assist_security_check_version",
|
||||
"ajax_nonce" => "silver_assist_security_ajax",
|
||||
"text_domain" => "silver-assist-security" // Your plugin's text domain
|
||||
"text_domain" => "silver-assist-security", // Your plugin's text domain
|
||||
"custom_temp_dir" => WP_CONTENT_DIR . "/temp", // Enhanced error handling (v1.1.3+)
|
||||
]
|
||||
);
|
||||
|
||||
new Updater($config);
|
||||
$updater = new Updater($config);
|
||||
|
||||
// Optional: Programmatic version checking (v1.1.2+)
|
||||
if ($updater->isUpdateAvailable()) {
|
||||
// Handle update availability programmatically
|
||||
$latestVersion = $updater->getLatestVersion();
|
||||
error_log("Security plugin update available: " . $latestVersion);
|
||||
}
|
||||
});
|
||||
*/
|
||||
|
||||
|
|
@ -59,11 +67,19 @@ add_action("init", function() {
|
|||
"asset_pattern" => "leadgen-app-form-v{version}.zip",
|
||||
"ajax_action" => "leadgen_check_version",
|
||||
"ajax_nonce" => "leadgen_version_check",
|
||||
"text_domain" => "leadgen-app-form" // Your plugin's text domain
|
||||
"text_domain" => "leadgen-app-form", // Your plugin's text domain
|
||||
"custom_temp_dir" => wp_upload_dir()["basedir"] . "/temp", // Alternative temp dir location
|
||||
]
|
||||
);
|
||||
|
||||
new Updater($config);
|
||||
$updater = new Updater($config);
|
||||
|
||||
// Optional: Add manual check button in admin
|
||||
add_action("admin_init", function() use ($updater) {
|
||||
if (isset($_GET["leadgen_check_update"]) && current_user_can("update_plugins")) {
|
||||
$updater->manualVersionCheck();
|
||||
}
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
|
|
@ -102,14 +118,76 @@ add_action("init", function() {
|
|||
"requires_wordpress" => "6.2",
|
||||
"ajax_action" => "my_plugin_version_check",
|
||||
"cache_duration" => 6 * 3600, // 6 hours
|
||||
"text_domain" => "my-new-plugin" // Your plugin's text domain
|
||||
"text_domain" => "my-new-plugin", // Your plugin's text domain
|
||||
"custom_temp_dir" => WP_CONTENT_DIR . "/temp", // Improved hosting compatibility
|
||||
]
|
||||
);
|
||||
|
||||
$updater = new Updater($config);
|
||||
|
||||
// Example: Check for updates programmatically
|
||||
add_action("admin_notices", function() use ($updater) {
|
||||
if (!current_user_can("update_plugins")) return;
|
||||
|
||||
if ($updater->isUpdateAvailable()) {
|
||||
$currentVersion = $updater->getCurrentVersion();
|
||||
$latestVersion = $updater->getLatestVersion();
|
||||
|
||||
echo '<div class="notice notice-info">';
|
||||
echo '<p>My Plugin: Update available from ' . esc_html($currentVersion) . ' to ' . esc_html($latestVersion) . '</p>';
|
||||
echo '</div>';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Your plugin code here...
|
||||
*/
|
||||
|
||||
/**
|
||||
* Example for plugins with PCLZIP_ERR_MISSING_FILE issues
|
||||
* Use custom temporary directory to avoid /tmp permission problems
|
||||
*/
|
||||
|
||||
/*
|
||||
// For plugins experiencing PCLZIP_ERR_MISSING_FILE errors:
|
||||
|
||||
add_action("init", function() {
|
||||
$config = new UpdaterConfig(
|
||||
__FILE__,
|
||||
"your-username/your-plugin",
|
||||
[
|
||||
"text_domain" => "your-plugin",
|
||||
"custom_temp_dir" => WP_CONTENT_DIR . "/temp", // Custom temp directory
|
||||
// or use uploads directory:
|
||||
// "custom_temp_dir" => wp_upload_dir()["basedir"] . "/temp",
|
||||
]
|
||||
);
|
||||
|
||||
new Updater($config);
|
||||
|
||||
// Optionally create the directory on plugin activation
|
||||
register_activation_hook(__FILE__, function() {
|
||||
$temp_dir = WP_CONTENT_DIR . "/temp";
|
||||
if (!file_exists($temp_dir)) {
|
||||
wp_mkdir_p($temp_dir);
|
||||
}
|
||||
});
|
||||
});
|
||||
*/
|
||||
|
||||
// Your plugin code here...
|
||||
/**
|
||||
* Alternative: WordPress configuration approach
|
||||
* Add this to your wp-config.php file (before the line that says
|
||||
* "That's all, stop editing!"):
|
||||
*/
|
||||
|
||||
/*
|
||||
// In wp-config.php, add this line:
|
||||
define('WP_TEMP_DIR', ABSPATH . 'wp-content/temp');
|
||||
|
||||
// Then create the directory with proper permissions:
|
||||
// mkdir wp-content/temp
|
||||
// chmod 755 wp-content/temp
|
||||
*/
|
||||
|
||||
/**
|
||||
|
|
@ -121,3 +199,22 @@ add_action("init", function() {
|
|||
* 4. Remove your old updater class files
|
||||
* 5. Test the updates
|
||||
*/
|
||||
|
||||
/**
|
||||
* New Features in v1.1.4:
|
||||
*
|
||||
* - WordPress admin notices for manual version checks
|
||||
* - Dismissible admin notices with AJAX functionality
|
||||
* - Improved code organization with isUpdateAvailable() method
|
||||
*
|
||||
* New Features in v1.1.3:
|
||||
*
|
||||
* - Enhanced temporary file handling to resolve PCLZIP errors
|
||||
* - Better error handling and hosting environment compatibility
|
||||
*
|
||||
* Public API methods (v1.1.2+):
|
||||
* - $updater->isUpdateAvailable() - Check if update is available
|
||||
* - $updater->getCurrentVersion() - Get current plugin version
|
||||
* - $updater->getLatestVersion() - Get latest GitHub version
|
||||
* - $updater->getGithubRepo() - Get repository name
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -47,6 +47,12 @@
|
|||
<!-- WordPress Security (Critical for WordPress integration) -->
|
||||
<rule ref="WordPress.Security.EscapeOutput"/>
|
||||
<rule ref="WordPress.Security.ValidatedSanitizedInput"/>
|
||||
|
||||
<!-- Exclude WordPress.Security checks from test files -->
|
||||
<rule ref="WordPress.Security">
|
||||
<exclude-pattern>tests/bootstrap.php</exclude-pattern>
|
||||
<exclude-pattern>tests/fixtures/*</exclude-pattern>
|
||||
</rule>
|
||||
|
||||
<!-- WordPress i18n (Text domain validation) -->
|
||||
<rule ref="WordPress.WP.I18n">
|
||||
|
|
|
|||
11
phpstan.neon
11
phpstan.neon
|
|
@ -1,9 +1,16 @@
|
|||
includes:
|
||||
- vendor/szepeviktor/phpstan-wordpress/extension.neon
|
||||
|
||||
parameters:
|
||||
level: 8
|
||||
paths:
|
||||
- src
|
||||
excludePaths:
|
||||
- tests
|
||||
bootstrapFiles:
|
||||
- vendor/php-stubs/wordpress-stubs/wordpress-stubs.php
|
||||
scanDirectories:
|
||||
- vendor/php-stubs/wordpress-stubs
|
||||
ignoreErrors:
|
||||
# WordPress functions are not available during static analysis
|
||||
- '#Call to unknown function: [\\]?[a-zA-Z_]+#'
|
||||
# Ignore WP_Error union type issues in some contexts
|
||||
- '#Cannot call method get_error_message\(\) on string\|WP_Error\|false#'
|
||||
|
|
|
|||
34
phpunit.xml
34
phpunit.xml
|
|
@ -1,15 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phpunit bootstrap="tests/bootstrap.php"
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.6/phpunit.xsd"
|
||||
bootstrap="tests/bootstrap.php"
|
||||
backupGlobals="false"
|
||||
colors="true">
|
||||
colors="true"
|
||||
verbose="true">
|
||||
<testsuites>
|
||||
<testsuite name="WP GitHub Updater Test Suite">
|
||||
<directory>tests</directory>
|
||||
<testsuite name="unit">
|
||||
<directory>tests/Unit</directory>
|
||||
</testsuite>
|
||||
<testsuite name="integration">
|
||||
<directory>tests/Integration</directory>
|
||||
</testsuite>
|
||||
<testsuite name="wordpress">
|
||||
<directory>tests/WordPress</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<source>
|
||||
<coverage>
|
||||
<include>
|
||||
<directory suffix=".php">./src</directory>
|
||||
</include>
|
||||
</source>
|
||||
<report>
|
||||
<html outputDirectory="build/coverage"/>
|
||||
<text outputFile="php://stdout" showUncoveredFiles="false"/>
|
||||
</report>
|
||||
</coverage>
|
||||
<php>
|
||||
<!-- Environment variable for WordPress Test Suite directory -->
|
||||
<!-- Set this to enable WordPress integration tests -->
|
||||
<!-- Example: export WP_TESTS_DIR=/tmp/wordpress-tests-lib -->
|
||||
<env name="WP_TESTS_DIR" value="" force="false"/>
|
||||
|
||||
<!-- WordPress Core directory (auto-detected if not set) -->
|
||||
<env name="WP_CORE_DIR" value="" force="false"/>
|
||||
</php>
|
||||
</phpunit>
|
||||
|
|
|
|||
255
scripts/install-wp-tests.sh
Executable file
255
scripts/install-wp-tests.sh
Executable file
|
|
@ -0,0 +1,255 @@
|
|||
#!/usr/bin/env bash
|
||||
# WordPress Test Environment Installation Script
|
||||
# Based on WordPress Core test suite installer
|
||||
#
|
||||
# This script installs WordPress and the WordPress Test Suite for running PHPUnit tests
|
||||
# Usage: ./install-wp-tests.sh <db-name> <db-user> <db-pass> [db-host] [wp-version]
|
||||
|
||||
if [ $# -lt 3 ]; then
|
||||
echo ""
|
||||
echo "❌ ERROR: Missing required arguments"
|
||||
echo ""
|
||||
echo "Usage: $0 <db-name> <db-user> <db-pass> [db-host] [wp-version]"
|
||||
echo ""
|
||||
echo "Arguments:"
|
||||
echo " db-name Database name for tests (will be created/recreated)"
|
||||
echo " db-user MySQL username"
|
||||
echo " db-pass MySQL password (use '' for empty password)"
|
||||
echo " db-host MySQL host (default: localhost)"
|
||||
echo " wp-version WordPress version to install (default: 6.7.1)"
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo " $0 wordpress_test root '' localhost 6.7.1"
|
||||
echo ""
|
||||
exit 1
|
||||
fi
|
||||
|
||||
DB_NAME=$1
|
||||
DB_USER=$2
|
||||
DB_PASS=$3
|
||||
DB_HOST=${4-localhost}
|
||||
WP_VERSION=${5-6.7.1}
|
||||
SKIP_DB_CREATE=${6-false}
|
||||
|
||||
TMPDIR=${TMPDIR-/tmp}
|
||||
TMPDIR=$(echo $TMPDIR | sed -e "s/\/$//")
|
||||
WP_TESTS_DIR=${WP_TESTS_DIR-$TMPDIR/wordpress-tests-lib}
|
||||
WP_CORE_DIR=${WP_CORE_DIR-$TMPDIR/wordpress/}
|
||||
|
||||
echo ""
|
||||
echo "🚀 WordPress Test Suite Installer"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "Installing WordPress Test Environment..."
|
||||
echo ""
|
||||
echo "Configuration:"
|
||||
echo " Database Name: $DB_NAME"
|
||||
echo " Database User: $DB_USER"
|
||||
echo " Database Host: $DB_HOST"
|
||||
echo " WordPress Version: $WP_VERSION"
|
||||
echo " WP Tests Dir: $WP_TESTS_DIR"
|
||||
echo " WP Core Dir: $WP_CORE_DIR"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
|
||||
download() {
|
||||
if [ `which curl` ]; then
|
||||
curl -s "$1" > "$2";
|
||||
elif [ `which wget` ]; then
|
||||
wget -nv -O "$2" "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+\-(beta|RC)[0-9]+$ ]]; then
|
||||
WP_BRANCH=${WP_VERSION%\-*}
|
||||
WP_TESTS_TAG="branches/$WP_BRANCH"
|
||||
|
||||
elif [[ $WP_VERSION =~ ^[0-9]+\.[0-9]+$ ]]; then
|
||||
WP_TESTS_TAG="branches/$WP_VERSION"
|
||||
|
||||
elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0-9]+ ]]; then
|
||||
if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
|
||||
WP_TESTS_TAG="tags/${WP_VERSION%??}"
|
||||
else
|
||||
WP_TESTS_TAG="tags/$WP_VERSION"
|
||||
fi
|
||||
|
||||
elif [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' || $WP_VERSION == 'latest' ]]; then
|
||||
WP_TESTS_TAG="trunk"
|
||||
|
||||
else
|
||||
echo "Invalid version $WP_VERSION"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
set -ex
|
||||
|
||||
install_wp() {
|
||||
|
||||
if [ -d $WP_CORE_DIR ]; then
|
||||
echo "✅ WordPress core already installed at $WP_CORE_DIR"
|
||||
return;
|
||||
fi
|
||||
|
||||
echo "📥 Downloading WordPress $WP_VERSION..."
|
||||
mkdir -p $WP_CORE_DIR
|
||||
|
||||
if [[ $WP_VERSION == 'nightly' || $WP_VERSION == 'trunk' ]]; then
|
||||
mkdir -p $TMPDIR/wordpress-trunk
|
||||
rm -rf $TMPDIR/wordpress-trunk/*
|
||||
svn export --quiet https://core.svn.wordpress.org/trunk $TMPDIR/wordpress-trunk/wordpress
|
||||
mv $TMPDIR/wordpress-trunk/wordpress/* $WP_CORE_DIR
|
||||
else
|
||||
if [ $WP_VERSION == 'latest' ]; then
|
||||
local ARCHIVE_NAME='latest'
|
||||
elif [[ $WP_VERSION =~ [0-9]+\.[0-9]+ ]]; then
|
||||
if [[ $WP_VERSION =~ [0-9]+\.[0-9]+\.[0] ]]; then
|
||||
local ARCHIVE_NAME="${WP_VERSION%??}"
|
||||
else
|
||||
local ARCHIVE_NAME=$WP_VERSION
|
||||
fi
|
||||
else
|
||||
local ARCHIVE_NAME="wordpress-$WP_VERSION"
|
||||
fi
|
||||
download https://wordpress.org/wordpress-${ARCHIVE_NAME}.tar.gz $TMPDIR/wordpress.tar.gz
|
||||
tar --strip-components=1 -zxmf $TMPDIR/wordpress.tar.gz -C $WP_CORE_DIR
|
||||
fi
|
||||
|
||||
echo "✅ WordPress $WP_VERSION downloaded successfully"
|
||||
|
||||
echo "📥 Downloading mysqli drop-in..."
|
||||
download https://raw.githubusercontent.com/markoheijnen/wp-mysqli/master/db.php $WP_CORE_DIR/wp-content/db.php
|
||||
echo "✅ mysqli drop-in installed"
|
||||
}
|
||||
|
||||
install_test_suite() {
|
||||
if [ -d $WP_TESTS_DIR ]; then
|
||||
echo "✅ WordPress Test Suite already installed at $WP_TESTS_DIR"
|
||||
return;
|
||||
fi
|
||||
|
||||
echo "📥 Downloading WordPress Test Suite from SVN..."
|
||||
mkdir -p $WP_TESTS_DIR
|
||||
|
||||
rm -rf $WP_TESTS_DIR/{includes,data}
|
||||
|
||||
svn export --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/includes/ $WP_TESTS_DIR/includes
|
||||
svn export --quiet --ignore-externals https://develop.svn.wordpress.org/${WP_TESTS_TAG}/tests/phpunit/data/ $WP_TESTS_DIR/data
|
||||
|
||||
echo "✅ WordPress Test Suite downloaded successfully"
|
||||
|
||||
echo "📝 Generating wp-tests-config.php..."
|
||||
if [ ! -f wp-tests-config.php ]; then
|
||||
download https://develop.svn.wordpress.org/${WP_TESTS_TAG}/wp-tests-config-sample.php "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
WP_CORE_DIR=$(echo $WP_CORE_DIR | sed 's:/\+$::')
|
||||
sed $ioption "s:dirname( __FILE__ ) . '/src/':'$WP_CORE_DIR/':" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed $ioption "s:__FILE__:'$WP_TESTS_DIR/wp-tests-config.php':" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed $ioption "s/youremptytestdbnamehere/$DB_NAME/" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed $ioption "s/yourusernamehere/$DB_USER/" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed $ioption "s/yourpasswordhere/$DB_PASS/" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
sed $ioption "s|localhost|${DB_HOST}|" "$WP_TESTS_DIR"/wp-tests-config.php
|
||||
fi
|
||||
echo "✅ wp-tests-config.php generated"
|
||||
}
|
||||
|
||||
recreate_db() {
|
||||
shopt -s nocasematch
|
||||
if [[ $1 =~ ^(y|yes)$ ]]
|
||||
then
|
||||
mysqladmin drop $DB_NAME -f --user="$DB_USER" --password="$DB_PASS"$EXTRA
|
||||
create_db
|
||||
echo "Recreated the database ($DB_NAME)."
|
||||
else
|
||||
echo "Skipping database recreation."
|
||||
exit 1
|
||||
fi
|
||||
shopt -u nocasematch
|
||||
}
|
||||
|
||||
create_db() {
|
||||
mysqladmin create $DB_NAME --user="$DB_USER" --password="$DB_PASS"$EXTRA
|
||||
}
|
||||
|
||||
install_db() {
|
||||
if [ ${SKIP_DB_CREATE} = "true" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Use DB_HOST for compatibility (script historically used DB_HOSTNAME)
|
||||
DB_HOSTNAME=${DB_HOST}
|
||||
|
||||
EXTRA=""
|
||||
|
||||
if ! [ -z "$DB_HOSTNAME" ] ; then
|
||||
# If hostname starts with /, it's a socket path
|
||||
if [[ $DB_HOSTNAME == /* ]] ; then
|
||||
EXTRA=" --socket=$DB_HOSTNAME"
|
||||
# If hostname contains a colon, it's host:port
|
||||
elif [[ $DB_HOSTNAME == *:* ]] ; then
|
||||
EXTRA=" --host=$(echo $DB_HOSTNAME | cut -d: -f1) --port=$(echo $DB_HOSTNAME | cut -d: -f2) --protocol=tcp"
|
||||
# Otherwise it's just a hostname or IP - use TCP
|
||||
else
|
||||
EXTRA=" --host=$DB_HOSTNAME --protocol=tcp"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -n "`mysql --user="$DB_USER" --password="$DB_PASS"$EXTRA --execute='show databases;' | grep ^$DB_NAME$`" ]
|
||||
then
|
||||
# In CI/CD or non-interactive mode, automatically recreate database
|
||||
if [ -t 0 ]; then
|
||||
# Interactive mode - ask for confirmation
|
||||
echo ""
|
||||
echo "⚠️ DATABASE ALREADY EXISTS"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "The database '$DB_NAME' already exists in your MySQL server."
|
||||
echo ""
|
||||
echo "WordPress Test Suite requires a clean database installation."
|
||||
echo "The existing database will be DROPPED and recreated."
|
||||
echo ""
|
||||
echo "⚠️ WARNING: This will DELETE all data in the '$DB_NAME' database!"
|
||||
echo ""
|
||||
echo "If this is a production database or contains important data,"
|
||||
echo "press Ctrl+C now to cancel, or type 'N' below."
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
read -p "Are you sure you want to proceed? [y/N]: " DELETE_EXISTING_DB
|
||||
recreate_db $DELETE_EXISTING_DB
|
||||
else
|
||||
# Non-interactive mode (CI/CD) - automatically recreate
|
||||
echo "🔄 Database already exists - automatically recreating for test environment..."
|
||||
mysqladmin drop $DB_NAME -f --user="$DB_USER" --password="$DB_PASS"$EXTRA
|
||||
create_db
|
||||
echo "✅ Database recreated successfully"
|
||||
fi
|
||||
else
|
||||
create_db
|
||||
fi
|
||||
}
|
||||
|
||||
case $(uname -s) in
|
||||
Darwin)
|
||||
ioption='-i.bak'
|
||||
;;
|
||||
*)
|
||||
ioption='-i'
|
||||
;;
|
||||
esac
|
||||
|
||||
install_wp
|
||||
install_test_suite
|
||||
install_db
|
||||
|
||||
echo ""
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "✅ WordPress Test Suite installed successfully!"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo ""
|
||||
echo "Environment Variables:"
|
||||
echo " WP_TESTS_DIR: $WP_TESTS_DIR"
|
||||
echo " WP_CORE_DIR: $WP_CORE_DIR"
|
||||
echo ""
|
||||
echo "Add this to your phpunit.xml.dist:"
|
||||
echo " <const name=\"WP_TESTS_DIR\" value=\"$WP_TESTS_DIR\"/>"
|
||||
echo ""
|
||||
echo "Now you can run tests with:"
|
||||
echo " vendor/bin/phpunit --testdox"
|
||||
echo ""
|
||||
231
scripts/test-runner.sh
Executable file
231
scripts/test-runner.sh
Executable file
|
|
@ -0,0 +1,231 @@
|
|||
#!/bin/bash
|
||||
# WordPress Test Suite Setup and Test Runner
|
||||
# This script helps install WordPress Test Suite and run tests
|
||||
|
||||
set -e
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
# Script directory
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
PROJECT_ROOT="$(dirname "$SCRIPT_DIR")"
|
||||
|
||||
echo -e "${BLUE}================================${NC}"
|
||||
echo -e "${BLUE}WP GitHub Updater Test Runner${NC}"
|
||||
echo -e "${BLUE}================================${NC}"
|
||||
echo ""
|
||||
|
||||
# Function to check if WordPress Test Suite is installed
|
||||
check_wp_tests() {
|
||||
local tests_dir="${WP_TESTS_DIR:-/tmp/wordpress-tests-lib}"
|
||||
|
||||
if [ -d "$tests_dir" ] && [ -f "$tests_dir/includes/functions.php" ]; then
|
||||
return 0
|
||||
else
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to install WordPress Test Suite
|
||||
install_wp_tests() {
|
||||
echo -e "${YELLOW}WordPress Test Suite not found${NC}"
|
||||
echo ""
|
||||
echo "To run WordPress integration tests, you need to install WordPress Test Suite."
|
||||
echo ""
|
||||
echo -e "${GREEN}Installation command:${NC}"
|
||||
echo " ./scripts/install-wp-tests.sh <db-name> <db-user> <db-pass> [db-host] [wp-version]"
|
||||
echo ""
|
||||
echo -e "${GREEN}Example:${NC}"
|
||||
echo " ./scripts/install-wp-tests.sh wordpress_test root '' localhost 6.7.1"
|
||||
echo ""
|
||||
read -p "Do you want to install now? (y/N) " -n 1 -r
|
||||
echo
|
||||
|
||||
if [[ $REPLY =~ ^[Yy]$ ]]; then
|
||||
echo ""
|
||||
echo "Please provide database credentials:"
|
||||
read -p "Database name (default: wordpress_test): " db_name
|
||||
db_name=${db_name:-wordpress_test}
|
||||
|
||||
read -p "Database user (default: root): " db_user
|
||||
db_user=${db_user:-root}
|
||||
|
||||
read -sp "Database password (default: empty): " db_pass
|
||||
echo
|
||||
|
||||
read -p "Database host (default: localhost): " db_host
|
||||
db_host=${db_host:-localhost}
|
||||
|
||||
read -p "WordPress version (default: 6.7.1): " wp_version
|
||||
wp_version=${wp_version:-6.7.1}
|
||||
|
||||
echo ""
|
||||
echo -e "${BLUE}Installing WordPress Test Suite...${NC}"
|
||||
"$PROJECT_ROOT/scripts/install-wp-tests.sh" "$db_name" "$db_user" "$db_pass" "$db_host" "$wp_version"
|
||||
|
||||
if [ $? -eq 0 ]; then
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ WordPress Test Suite installed successfully!${NC}"
|
||||
return 0
|
||||
else
|
||||
echo ""
|
||||
echo -e "${RED}✗ Failed to install WordPress Test Suite${NC}"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo ""
|
||||
echo "Skipping WordPress Test Suite installation."
|
||||
echo "You can install it later using the command above."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to run specific test suite
|
||||
run_tests() {
|
||||
local suite=$1
|
||||
local phpunit="$PROJECT_ROOT/vendor/bin/phpunit"
|
||||
|
||||
if [ ! -f "$phpunit" ]; then
|
||||
echo -e "${RED}✗ PHPUnit not found. Please run: composer install${NC}"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
cd "$PROJECT_ROOT"
|
||||
|
||||
case $suite in
|
||||
unit)
|
||||
echo -e "${BLUE}Running Unit Tests...${NC}"
|
||||
"$phpunit" --testsuite=unit
|
||||
;;
|
||||
integration)
|
||||
echo -e "${BLUE}Running Integration Tests...${NC}"
|
||||
"$phpunit" --testsuite=integration
|
||||
;;
|
||||
wordpress)
|
||||
echo -e "${BLUE}Running WordPress Tests...${NC}"
|
||||
if check_wp_tests; then
|
||||
echo -e "${GREEN}✓ WordPress Test Suite found${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ WordPress Test Suite not found${NC}"
|
||||
echo "WordPress tests will run with mocked functions."
|
||||
echo ""
|
||||
fi
|
||||
"$phpunit" --testsuite=wordpress
|
||||
;;
|
||||
all)
|
||||
echo -e "${BLUE}Running All Tests...${NC}"
|
||||
"$phpunit"
|
||||
;;
|
||||
coverage)
|
||||
echo -e "${BLUE}Running Tests with Coverage...${NC}"
|
||||
if command -v php -m | grep -q xdebug; then
|
||||
"$phpunit" --coverage-html build/coverage --coverage-text
|
||||
echo ""
|
||||
echo -e "${GREEN}✓ Coverage report generated in: build/coverage/index.html${NC}"
|
||||
else
|
||||
echo -e "${YELLOW}⚠️ Xdebug not found. Installing PCOV...${NC}"
|
||||
echo "Please install Xdebug or PCOV for code coverage:"
|
||||
echo " - Xdebug: pecl install xdebug"
|
||||
echo " - PCOV: pecl install pcov"
|
||||
exit 1
|
||||
fi
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Unknown test suite: $suite${NC}"
|
||||
show_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Function to show test status
|
||||
show_status() {
|
||||
echo -e "${BLUE}Test Environment Status:${NC}"
|
||||
echo ""
|
||||
|
||||
# PHPUnit
|
||||
if [ -f "$PROJECT_ROOT/vendor/bin/phpunit" ]; then
|
||||
echo -e "PHPUnit: ${GREEN}✓ Installed${NC}"
|
||||
phpunit_version=$("$PROJECT_ROOT/vendor/bin/phpunit" --version | head -n 1)
|
||||
echo " $phpunit_version"
|
||||
else
|
||||
echo -e "PHPUnit: ${RED}✗ Not installed${NC}"
|
||||
echo " Run: composer install"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# WordPress Test Suite
|
||||
if check_wp_tests; then
|
||||
echo -e "WP Tests: ${GREEN}✓ Installed${NC}"
|
||||
tests_dir="${WP_TESTS_DIR:-/tmp/wordpress-tests-lib}"
|
||||
echo " Location: $tests_dir"
|
||||
else
|
||||
echo -e "WP Tests: ${YELLOW}⚠️ Not installed${NC}"
|
||||
echo " Run: $0 install"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Mock Plugin
|
||||
mock_plugin="$PROJECT_ROOT/tests/fixtures/mock-plugin/mock-plugin.php"
|
||||
if [ -f "$mock_plugin" ]; then
|
||||
echo -e "Mock Plugin: ${GREEN}✓ Available${NC}"
|
||||
echo " Location: tests/fixtures/mock-plugin/"
|
||||
else
|
||||
echo -e "Mock Plugin: ${RED}✗ Not found${NC}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Function to show usage
|
||||
show_usage() {
|
||||
echo "Usage: $0 [command]"
|
||||
echo ""
|
||||
echo "Commands:"
|
||||
echo " status Show test environment status"
|
||||
echo " install Install WordPress Test Suite"
|
||||
echo " unit Run unit tests only"
|
||||
echo " integration Run integration tests only"
|
||||
echo " wordpress Run WordPress tests only"
|
||||
echo " all Run all tests (default)"
|
||||
echo " coverage Run tests with code coverage"
|
||||
echo " help Show this help message"
|
||||
echo ""
|
||||
echo "Examples:"
|
||||
echo " $0 status # Check environment"
|
||||
echo " $0 install # Install WP Test Suite"
|
||||
echo " $0 unit # Run unit tests"
|
||||
echo " $0 # Run all tests"
|
||||
echo ""
|
||||
}
|
||||
|
||||
# Main script logic
|
||||
case ${1:-all} in
|
||||
status)
|
||||
show_status
|
||||
;;
|
||||
install)
|
||||
install_wp_tests
|
||||
;;
|
||||
unit|integration|wordpress|all|coverage)
|
||||
run_tests "$1"
|
||||
;;
|
||||
help|-h|--help)
|
||||
show_usage
|
||||
;;
|
||||
*)
|
||||
echo -e "${RED}Unknown command: $1${NC}"
|
||||
echo ""
|
||||
show_usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
exit 0
|
||||
357
src/Updater.php
357
src/Updater.php
|
|
@ -7,12 +7,15 @@
|
|||
*
|
||||
* @package SilverAssist\WpGithubUpdater
|
||||
* @author Silver Assist
|
||||
* @version 1.1.1
|
||||
* @version 1.2.1
|
||||
* @license PolyForm-Noncommercial-1.0.0
|
||||
*/
|
||||
|
||||
namespace SilverAssist\WpGithubUpdater;
|
||||
|
||||
use WP_Error;
|
||||
use WP_Upgrader;
|
||||
|
||||
/**
|
||||
* Main updater class that handles plugin updates from GitHub releases
|
||||
*
|
||||
|
|
@ -115,6 +118,12 @@ class Updater
|
|||
|
||||
// Add AJAX action for manual version check
|
||||
\add_action("wp_ajax_{$this->config->ajaxAction}", [$this, "manualVersionCheck"]);
|
||||
|
||||
// Add AJAX action for dismissing update notices
|
||||
\add_action("wp_ajax_{$this->config->ajaxAction}_dismiss_notice", [$this, "dismissUpdateNotice"]);
|
||||
|
||||
// Add admin notice for manual version checks
|
||||
\add_action("admin_notices", [$this, "showUpdateNotice"]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -136,7 +145,7 @@ class Updater
|
|||
|
||||
$latestVersion = $this->getLatestVersion();
|
||||
|
||||
if ($latestVersion && version_compare($this->currentVersion, $latestVersion, "<")) {
|
||||
if ($this->isUpdateAvailable()) {
|
||||
$transient->response[$this->pluginSlug] = (object) [
|
||||
"slug" => $this->pluginBasename,
|
||||
"plugin" => $this->pluginSlug,
|
||||
|
|
@ -203,7 +212,7 @@ class Updater
|
|||
*
|
||||
* @since 1.0.0
|
||||
*/
|
||||
private function getLatestVersion(): string|false
|
||||
public function getLatestVersion(): string|false
|
||||
{
|
||||
// Check cache first
|
||||
$cachedVersion = \get_transient($this->versionTransient);
|
||||
|
|
@ -384,10 +393,10 @@ class Updater
|
|||
/**
|
||||
* Clear version cache after update
|
||||
*
|
||||
* @param \WP_Upgrader $upgrader WP_Upgrader instance
|
||||
* @param WP_Upgrader $upgrader WP_Upgrader instance
|
||||
* @param array $data Array of update data
|
||||
*/
|
||||
public function clearVersionCache(\WP_Upgrader $upgrader, array $data): void
|
||||
public function clearVersionCache(WP_Upgrader $upgrader, array $data): void
|
||||
{
|
||||
if ($data["action"] === "update" && $data["type"] === "plugin") {
|
||||
if (isset($data["plugins"]) && in_array($this->pluginSlug, $data["plugins"])) {
|
||||
|
|
@ -418,14 +427,31 @@ class Updater
|
|||
}
|
||||
|
||||
try {
|
||||
// Clear our version cache
|
||||
\delete_transient($this->versionTransient);
|
||||
|
||||
// Clear WordPress update cache to force refresh
|
||||
\delete_site_transient("update_plugins");
|
||||
|
||||
$latestVersion = $this->getLatestVersion();
|
||||
$updateAvailable = $this->isUpdateAvailable();
|
||||
|
||||
// If update is available, set a transient to show admin notice
|
||||
if ($updateAvailable) {
|
||||
\set_transient("wp_github_updater_notice_{$this->pluginBasename}", [
|
||||
"plugin_name" => $this->config->pluginName,
|
||||
"current_version" => $this->currentVersion,
|
||||
"latest_version" => $latestVersion,
|
||||
"github_repo" => $this->config->githubRepo,
|
||||
], $this->config->cacheDuration); // Use same duration as version cache
|
||||
}
|
||||
|
||||
\wp_send_json_success([
|
||||
"current_version" => $this->currentVersion,
|
||||
"latest_version" => $latestVersion ?: $this->config->__("Unknown"),
|
||||
"update_available" => $latestVersion && version_compare($this->currentVersion, $latestVersion, "<"),
|
||||
"update_available" => $updateAvailable,
|
||||
"github_repo" => $this->config->githubRepo,
|
||||
"notice_set" => $updateAvailable, // Indicate if notice was set
|
||||
]);
|
||||
} catch (\Exception $e) {
|
||||
\wp_send_json_error([
|
||||
|
|
@ -435,6 +461,99 @@ class Updater
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle dismissal of update notices via AJAX
|
||||
*
|
||||
* @since 1.1.4
|
||||
* @return void
|
||||
*/
|
||||
public function dismissUpdateNotice(): void
|
||||
{
|
||||
// Check nonce
|
||||
$nonce = \sanitize_text_field(\wp_unslash($_POST["nonce"] ?? ""));
|
||||
if (!\wp_verify_nonce($nonce, $this->config->ajaxNonce)) {
|
||||
\wp_die("Security verification failed", "Error", ["response" => 403]);
|
||||
}
|
||||
|
||||
// Check capabilities
|
||||
if (!\current_user_can("update_plugins")) {
|
||||
\wp_die("Insufficient permissions", "Error", ["response" => 403]);
|
||||
}
|
||||
|
||||
// Delete the notice transient
|
||||
$noticeKey = "wp_github_updater_notice_{$this->pluginBasename}";
|
||||
\delete_transient($noticeKey);
|
||||
|
||||
\wp_send_json_success(["message" => "Notice dismissed successfully"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Show admin notice for available updates
|
||||
*
|
||||
* Displays a WordPress admin notice when an update is available
|
||||
* after a manual version check.
|
||||
*
|
||||
* @since 1.1.4
|
||||
*/
|
||||
public function showUpdateNotice(): void
|
||||
{
|
||||
// Only show on admin pages
|
||||
if (!is_admin()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we have a notice to show
|
||||
$notice_data = \get_transient("wp_github_updater_notice_{$this->pluginBasename}");
|
||||
if (!$notice_data) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only show to users who can update plugins
|
||||
if (!\current_user_can("update_plugins")) {
|
||||
return;
|
||||
}
|
||||
|
||||
$plugin_name = $notice_data["plugin_name"] ?? $this->config->pluginName;
|
||||
$current_version = $notice_data["current_version"] ?? $this->currentVersion;
|
||||
$latest_version = $notice_data["latest_version"] ?? "Unknown";
|
||||
$github_repo = $notice_data["github_repo"] ?? $this->config->githubRepo;
|
||||
|
||||
$updates_url = \admin_url("plugins.php?plugin_status=upgrade");
|
||||
$github_url = "https://github.com/{$github_repo}/releases/latest";
|
||||
|
||||
echo '<div class="notice notice-warning is-dismissible" data-notice="wp-github-updater-' .
|
||||
\esc_attr($this->pluginBasename) . '">';
|
||||
echo '<p>';
|
||||
echo '<strong>' . \esc_html($plugin_name) . '</strong> ';
|
||||
echo sprintf(
|
||||
\esc_html($this->config->__("has a new version available: %1\$s (you have %2\$s).")),
|
||||
'<strong>' . \esc_html($latest_version) . '</strong>',
|
||||
\esc_html($current_version)
|
||||
);
|
||||
echo '</p>';
|
||||
echo '<p>';
|
||||
echo '<a href="' . \esc_url($updates_url) . '" class="button button-primary">' .
|
||||
\esc_html($this->config->__("View Updates")) . '</a> ';
|
||||
echo '<a href="' . \esc_url($github_url) . '" class="button" target="_blank">' .
|
||||
\esc_html($this->config->__("View Release Notes")) . '</a>';
|
||||
echo '</p>';
|
||||
echo '</div>';
|
||||
|
||||
// Add JavaScript to handle dismissal
|
||||
echo '<script>
|
||||
jQuery(document).ready(function($) {
|
||||
$(document).on("click", "[data-notice=\"wp-github-updater-' .
|
||||
\esc_js($this->pluginBasename) . '\"] .notice-dismiss", function() {
|
||||
$.post(ajaxurl, {
|
||||
action: "' . \esc_js($this->config->ajaxAction) . '_dismiss_notice",
|
||||
nonce: "' . \esc_js(\wp_create_nonce($this->config->ajaxNonce)) . '",
|
||||
plugin: "' . \esc_js($this->pluginBasename) . '"
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin data from file
|
||||
*/
|
||||
|
|
@ -531,24 +650,61 @@ class Updater
|
|||
/**
|
||||
* Maybe fix download issues by providing better HTTP args
|
||||
*
|
||||
* @return boolean|\WP_Error $result
|
||||
* This filter intercepts the download process for our GitHub releases
|
||||
* to ensure proper temporary file handling and avoid PCLZIP errors.
|
||||
*
|
||||
* CRITICAL: This filter MUST return one of:
|
||||
* - false (or WP_Error): Let WordPress handle the download normally
|
||||
* - string: Path to an already-downloaded file for WordPress to use
|
||||
* - NEVER return true or any other type!
|
||||
*
|
||||
* @param boolean|WP_Error $result The result from previous filters
|
||||
* @param string $package The package URL being downloaded
|
||||
* @param object $upgrader The WP_Upgrader instance
|
||||
* @param array $hook_extra Extra hook data
|
||||
* @return string|WP_Error|false Path to downloaded file, WP_Error on failure, or false to continue
|
||||
*
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public function maybeFixDownload(
|
||||
bool|\WP_Error $result,
|
||||
bool|WP_Error $result,
|
||||
string $package,
|
||||
object $upgrader,
|
||||
array $hook_extra
|
||||
): bool|\WP_Error {
|
||||
// Only handle GitHub downloads for our plugin
|
||||
if (!str_contains($package, "github.com") || !str_contains($package, $this->config->githubRepo)) {
|
||||
): string|WP_Error|false {
|
||||
// If a previous filter already handled this, respect that decision
|
||||
if (\is_wp_error($result)) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
// Use wp_remote_get with better parameters
|
||||
// Only handle GitHub downloads for our specific repository
|
||||
if (
|
||||
empty($package) ||
|
||||
!str_contains($package, "github.com") ||
|
||||
!str_contains($package, $this->config->githubRepo)
|
||||
) {
|
||||
return false; // Let WordPress handle it normally
|
||||
}
|
||||
|
||||
// Additional safety check: ensure this is actually for our plugin
|
||||
$is_our_plugin = false;
|
||||
if (isset($hook_extra["plugin"]) && $hook_extra["plugin"] === $this->pluginSlug) {
|
||||
$is_our_plugin = true;
|
||||
} elseif (
|
||||
isset($hook_extra["plugins"]) &&
|
||||
is_array($hook_extra["plugins"]) &&
|
||||
in_array($this->pluginSlug, $hook_extra["plugins"])
|
||||
) {
|
||||
$is_our_plugin = true;
|
||||
}
|
||||
|
||||
if (!$is_our_plugin) {
|
||||
return false; // Not our plugin, let WordPress handle it
|
||||
}
|
||||
|
||||
// Download the package with optimized settings
|
||||
$args = [
|
||||
"timeout" => 300, // 5 minutes
|
||||
"timeout" => 300, // 5 minutes for large files
|
||||
"headers" => $this->getDownloadHeaders(),
|
||||
"sslverify" => true,
|
||||
"stream" => false,
|
||||
|
|
@ -558,32 +714,183 @@ class Updater
|
|||
$response = \wp_remote_get($package, $args);
|
||||
|
||||
if (\is_wp_error($response)) {
|
||||
return $response;
|
||||
return new WP_Error(
|
||||
"download_failed",
|
||||
sprintf(
|
||||
$this->config->__("Failed to download package: %s"),
|
||||
$response->get_error_message()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (200 !== \wp_remote_retrieve_response_code($response)) {
|
||||
return new \WP_Error("http_404", $this->config->__("Package not found"));
|
||||
$response_code = \wp_remote_retrieve_response_code($response);
|
||||
if (200 !== $response_code) {
|
||||
return new WP_Error(
|
||||
"http_error",
|
||||
sprintf(
|
||||
$this->config->__("Package download failed with HTTP code %d"),
|
||||
$response_code
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Write to temporary file
|
||||
$upload_dir = \wp_upload_dir();
|
||||
$temp_file = \wp_tempnam(basename($package), $upload_dir["basedir"] . "/");
|
||||
|
||||
if (!$temp_file) {
|
||||
return new \WP_Error("temp_file_failed", $this->config->__("Could not create temporary file"));
|
||||
// Get the response body
|
||||
$body = \wp_remote_retrieve_body($response);
|
||||
if (empty($body)) {
|
||||
return new WP_Error(
|
||||
"empty_response",
|
||||
$this->config->__("Downloaded package is empty")
|
||||
);
|
||||
}
|
||||
|
||||
$file_handle = @fopen($temp_file, "w");
|
||||
// Create temporary file with our multi-tier fallback system
|
||||
$temp_file = $this->createSecureTempFile($package);
|
||||
|
||||
if (\is_wp_error($temp_file)) {
|
||||
return $temp_file;
|
||||
}
|
||||
|
||||
// Write the downloaded content to the temporary file
|
||||
$file_handle = @fopen($temp_file, "wb");
|
||||
if (!$file_handle) {
|
||||
return new \WP_Error("file_open_failed", $this->config->__("Could not open file for writing"));
|
||||
@unlink($temp_file); // Clean up if file was created but can't be opened
|
||||
return new WP_Error(
|
||||
"file_open_failed",
|
||||
sprintf(
|
||||
$this->config->__("Could not open temporary file for writing: %s"),
|
||||
$temp_file
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fwrite($file_handle, \wp_remote_retrieve_body($response));
|
||||
$bytes_written = fwrite($file_handle, $body);
|
||||
fclose($file_handle);
|
||||
|
||||
// Verify write operation succeeded
|
||||
if ($bytes_written === false || $bytes_written !== strlen($body)) {
|
||||
@unlink($temp_file);
|
||||
return new WP_Error(
|
||||
"file_write_failed",
|
||||
$this->config->__("Failed to write complete package to temporary file")
|
||||
);
|
||||
}
|
||||
|
||||
// Final verification: ensure file exists and is readable
|
||||
if (!file_exists($temp_file)) {
|
||||
return new WP_Error(
|
||||
"file_missing",
|
||||
$this->config->__("Temporary file disappeared after creation")
|
||||
);
|
||||
}
|
||||
|
||||
if (!is_readable($temp_file)) {
|
||||
@unlink($temp_file);
|
||||
return new WP_Error(
|
||||
"file_not_readable",
|
||||
$this->config->__("Temporary file is not readable")
|
||||
);
|
||||
}
|
||||
|
||||
// Verify it's actually a zip file
|
||||
$file_size = filesize($temp_file);
|
||||
if ($file_size < 100) { // Minimum size for a valid zip
|
||||
@unlink($temp_file);
|
||||
return new WP_Error(
|
||||
"invalid_package",
|
||||
sprintf(
|
||||
$this->config->__("Downloaded file is too small (%d bytes) to be a valid package"),
|
||||
$file_size
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// SUCCESS: Return the path to the downloaded file
|
||||
// WordPress will use this file for extraction
|
||||
return $temp_file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a secure temporary file with multiple fallback strategies
|
||||
*
|
||||
* Attempts different approaches to create a temporary file to avoid PCLZIP errors
|
||||
* that can occur with restrictive /tmp directory permissions.
|
||||
*
|
||||
* @param string $package The package URL being downloaded
|
||||
* @return string|WP_Error Path to temporary file or WP_Error on failure
|
||||
*
|
||||
* @since 1.1.4
|
||||
*/
|
||||
private function createSecureTempFile(string $package): string|WP_Error
|
||||
{
|
||||
$filename = basename(parse_url($package, PHP_URL_PATH)) ?: "github-package.zip";
|
||||
|
||||
// Strategy 1: Use custom temporary directory if specified
|
||||
if (!empty($this->config->customTempDir)) {
|
||||
if (!is_dir($this->config->customTempDir)) {
|
||||
@wp_mkdir_p($this->config->customTempDir);
|
||||
}
|
||||
|
||||
if (is_dir($this->config->customTempDir) && is_writable($this->config->customTempDir)) {
|
||||
$temp_file = \wp_tempnam($filename, $this->config->customTempDir . "/");
|
||||
if ($temp_file) {
|
||||
return $temp_file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 2: Use WordPress uploads directory
|
||||
$upload_dir = \wp_upload_dir();
|
||||
if (!empty($upload_dir["basedir"]) && is_writable($upload_dir["basedir"])) {
|
||||
$temp_file = \wp_tempnam($filename, $upload_dir["basedir"] . "/");
|
||||
if ($temp_file) {
|
||||
return $temp_file;
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 3: Use WP_CONTENT_DIR/temp if it exists or can be created
|
||||
$wp_content_temp = WP_CONTENT_DIR . "/temp";
|
||||
if (!is_dir($wp_content_temp)) {
|
||||
@wp_mkdir_p($wp_content_temp);
|
||||
}
|
||||
|
||||
if (is_dir($wp_content_temp) && is_writable($wp_content_temp)) {
|
||||
$temp_file = \wp_tempnam($filename, $wp_content_temp . "/");
|
||||
if ($temp_file) {
|
||||
return $temp_file;
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 4: Use WordPress temporary directory (if defined)
|
||||
if (defined("WP_TEMP_DIR") && is_dir(WP_TEMP_DIR) && is_writable(WP_TEMP_DIR)) {
|
||||
$temp_file = \wp_tempnam($filename, WP_TEMP_DIR . "/");
|
||||
if ($temp_file) {
|
||||
return $temp_file;
|
||||
}
|
||||
}
|
||||
|
||||
// Strategy 5: Try system temp directory as last resort
|
||||
$temp_file = \wp_tempnam($filename);
|
||||
if ($temp_file) {
|
||||
return $temp_file;
|
||||
}
|
||||
|
||||
// Strategy 6: Manual temp file creation in uploads dir
|
||||
if (!empty($upload_dir["basedir"])) {
|
||||
$manual_temp = $upload_dir["basedir"] . "/" . uniqid("wp_github_updater_", true) . ".tmp";
|
||||
$handle = @fopen($manual_temp, "w");
|
||||
if ($handle) {
|
||||
fclose($handle);
|
||||
return $manual_temp;
|
||||
}
|
||||
}
|
||||
|
||||
return new WP_Error(
|
||||
"temp_file_creation_failed",
|
||||
$this->config->__("Could not create temporary file. Please check directory permissions " .
|
||||
"or define WP_TEMP_DIR in wp-config.php")
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get headers for GitHub API requests
|
||||
*
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
*
|
||||
* @package SilverAssist\WpGithubUpdater
|
||||
* @author Silver Assist
|
||||
* @version 1.1.1
|
||||
* @version 1.2.1
|
||||
* @license PolyForm-Noncommercial-1.0.0
|
||||
*/
|
||||
|
||||
|
|
@ -130,6 +130,14 @@ class UpdaterConfig
|
|||
*/
|
||||
public string $textDomain;
|
||||
|
||||
/**
|
||||
* Custom temporary directory path
|
||||
*
|
||||
* @var string|null Custom path for temporary files during downloads, null for auto-detection
|
||||
* @since 1.1.3
|
||||
*/
|
||||
public ?string $customTempDir;
|
||||
|
||||
/**
|
||||
* Create updater configuration
|
||||
*
|
||||
|
|
@ -156,12 +164,13 @@ class UpdaterConfig
|
|||
$this->pluginAuthor = $options["plugin_author"] ?? $pluginData["Author"] ?? "";
|
||||
$this->pluginHomepage = $options["plugin_homepage"] ?? "https://github.com/{$githubRepo}";
|
||||
$this->requiresWordPress = $options["requires_wordpress"] ?? "6.0";
|
||||
$this->requiresPHP = $options["requires_php"] ?? "8.0";
|
||||
$this->requiresPHP = $options["requires_php"] ?? "8.2";
|
||||
$this->assetPattern = $options["asset_pattern"] ?? "{slug}-v{version}.zip";
|
||||
$this->cacheDuration = $options["cache_duration"] ?? (12 * 3600); // 12 hours
|
||||
$this->ajaxAction = $options["ajax_action"] ?? "check_plugin_version";
|
||||
$this->ajaxNonce = $options["ajax_nonce"] ?? "plugin_version_check";
|
||||
$this->textDomain = $options["text_domain"] ?? "wp-github-updater";
|
||||
$this->customTempDir = $options["custom_temp_dir"] ?? null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -177,7 +186,7 @@ class UpdaterConfig
|
|||
*/
|
||||
private function getPluginData(string $pluginFile): array
|
||||
{
|
||||
if (\function_exists("get_plugin_data")) {
|
||||
if (function_exists("get_plugin_data")) {
|
||||
return \get_plugin_data($pluginFile);
|
||||
}
|
||||
|
||||
|
|
|
|||
147
tests/Integration/DownloadFilterTest.php
Normal file
147
tests/Integration/DownloadFilterTest.php
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Integration tests for download filter functionality
|
||||
*
|
||||
* @package SilverAssist\WpGithubUpdater\Tests\Integration
|
||||
*/
|
||||
|
||||
namespace SilverAssist\WpGithubUpdater\Tests\Integration;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SilverAssist\WpGithubUpdater\Updater;
|
||||
use SilverAssist\WpGithubUpdater\UpdaterConfig;
|
||||
|
||||
/**
|
||||
* Test download filter functionality
|
||||
*
|
||||
* These tests verify the download filter behavior including
|
||||
* temporary file creation and validation.
|
||||
*/
|
||||
class DownloadFilterTest extends TestCase
|
||||
{
|
||||
private UpdaterConfig $config;
|
||||
private Updater $updater;
|
||||
private string $testPluginFile;
|
||||
|
||||
/**
|
||||
* Set up test environment before each test
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Create a temporary plugin file for testing
|
||||
$this->testPluginFile = sys_get_temp_dir() . "/test-plugin.php";
|
||||
file_put_contents($this->testPluginFile, "<?php\n/*\nPlugin Name: Test Plugin\nVersion: 1.0.0\n*/");
|
||||
|
||||
// Create test configuration
|
||||
$this->config = new UpdaterConfig($this->testPluginFile, "SilverAssist/test-repo");
|
||||
$this->updater = new Updater($this->config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after each test
|
||||
*/
|
||||
protected function tearDown(): void
|
||||
{
|
||||
if (file_exists($this->testPluginFile)) {
|
||||
unlink($this->testPluginFile);
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that temporary directory configuration is respected
|
||||
*/
|
||||
public function testCustomTempDirectoryIsRespected(): void
|
||||
{
|
||||
$customTempDir = sys_get_temp_dir() . "/wp-github-updater-custom";
|
||||
|
||||
$config = new UpdaterConfig($this->testPluginFile, "SilverAssist/test-repo", [
|
||||
"custom_temp_dir" => $customTempDir,
|
||||
]);
|
||||
|
||||
$this->assertEquals($customTempDir, $config->customTempDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that package URL validation works correctly
|
||||
*/
|
||||
public function testPackageUrlValidation(): void
|
||||
{
|
||||
// Valid GitHub URL
|
||||
$validUrl = "https://github.com/SilverAssist/test-repo/releases/download/v1.0.0/package.zip";
|
||||
$this->assertStringContainsString("github.com", $validUrl);
|
||||
$this->assertStringContainsString("SilverAssist/test-repo", $validUrl);
|
||||
|
||||
// Invalid URL (not GitHub)
|
||||
$invalidUrl = "https://example.com/package.zip";
|
||||
$this->assertStringNotContainsString("github.com", $invalidUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test file size validation logic
|
||||
*/
|
||||
public function testFileSizeValidation(): void
|
||||
{
|
||||
$minSize = 100; // Minimum size for valid ZIP
|
||||
|
||||
// Valid size
|
||||
$this->assertGreaterThanOrEqual($minSize, 1024);
|
||||
|
||||
// Invalid size
|
||||
$this->assertLessThan($minSize, 50);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that hook_extra validation logic works
|
||||
*/
|
||||
public function testHookExtraValidation(): void
|
||||
{
|
||||
$pluginSlug = basename(dirname($this->testPluginFile)) . "/" . basename($this->testPluginFile);
|
||||
|
||||
// Test single plugin update
|
||||
$hook_extra_single = [
|
||||
"plugin" => $pluginSlug,
|
||||
];
|
||||
$this->assertArrayHasKey("plugin", $hook_extra_single);
|
||||
$this->assertEquals($pluginSlug, $hook_extra_single["plugin"]);
|
||||
|
||||
// Test bulk plugin update
|
||||
$hook_extra_bulk = [
|
||||
"plugins" => [$pluginSlug, "other-plugin/other-plugin.php"],
|
||||
];
|
||||
$this->assertArrayHasKey("plugins", $hook_extra_bulk);
|
||||
$this->assertContains($pluginSlug, $hook_extra_bulk["plugins"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test version comparison logic
|
||||
*/
|
||||
public function testVersionComparison(): void
|
||||
{
|
||||
// Current version is older than latest
|
||||
$this->assertTrue(version_compare("1.0.0", "1.1.0", "<"));
|
||||
$this->assertFalse(version_compare("1.1.0", "1.0.0", "<"));
|
||||
|
||||
// Same version
|
||||
$this->assertFalse(version_compare("1.0.0", "1.0.0", "<"));
|
||||
$this->assertTrue(version_compare("1.0.0", "1.0.0", "="));
|
||||
}
|
||||
|
||||
/**
|
||||
* Test GitHub repository format validation
|
||||
*/
|
||||
public function testGitHubRepoFormat(): void
|
||||
{
|
||||
// Valid formats
|
||||
$this->assertMatchesRegularExpression("/^[a-zA-Z0-9-]+\/[a-zA-Z0-9-_]+$/", "SilverAssist/test-repo");
|
||||
$this->assertMatchesRegularExpression("/^[a-zA-Z0-9-]+\/[a-zA-Z0-9-_]+$/", "owner/repo");
|
||||
|
||||
// Invalid formats
|
||||
$this->assertDoesNotMatchRegularExpression("/^[a-zA-Z0-9-]+\/[a-zA-Z0-9-_]+$/", "invalid");
|
||||
$this->assertDoesNotMatchRegularExpression("/^[a-zA-Z0-9-]+\/[a-zA-Z0-9-_]+$/", "/owner/repo");
|
||||
}
|
||||
}
|
||||
423
tests/Integration/RealGitHubAPITest.php
Normal file
423
tests/Integration/RealGitHubAPITest.php
Normal file
|
|
@ -0,0 +1,423 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Real GitHub API Integration Tests
|
||||
*
|
||||
* Tests using real GitHub API with silver-assist-post-revalidate repository.
|
||||
* These tests make actual HTTP requests to GitHub's API.
|
||||
*
|
||||
* @package SilverAssist\WpGithubUpdater
|
||||
* @since 1.1.6
|
||||
*/
|
||||
|
||||
namespace SilverAssist\WpGithubUpdater\Tests\Integration;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SilverAssist\WpGithubUpdater\UpdaterConfig;
|
||||
use SilverAssist\WpGithubUpdater\Updater;
|
||||
|
||||
/**
|
||||
* Real GitHub API Integration Test Class
|
||||
*
|
||||
* Tests actual communication with GitHub's API using a real repository.
|
||||
*
|
||||
* @since 1.1.6
|
||||
*/
|
||||
class RealGitHubAPITest extends TestCase
|
||||
{
|
||||
/**
|
||||
* Test plugin file path
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private static string $testPluginFile;
|
||||
|
||||
/**
|
||||
* Set up before class
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
parent::setUpBeforeClass();
|
||||
self::$testPluginFile = dirname(__DIR__) . "/fixtures/test-plugin.php";
|
||||
}
|
||||
|
||||
/**
|
||||
* Test fetching latest version from real GitHub repository
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testFetchLatestVersionFromRealRepo(): void
|
||||
{
|
||||
$config = new UpdaterConfig(
|
||||
self::$testPluginFile,
|
||||
"SilverAssist/silver-assist-post-revalidate"
|
||||
);
|
||||
|
||||
$updater = new Updater($config);
|
||||
|
||||
// This makes a real HTTP request to GitHub
|
||||
$latestVersion = $updater->getLatestVersion();
|
||||
|
||||
// Should return the latest version or false
|
||||
$this->assertTrue(
|
||||
$latestVersion === false || is_string($latestVersion),
|
||||
"getLatestVersion() should return string or false"
|
||||
);
|
||||
|
||||
// If successful, should be a valid version string
|
||||
if ($latestVersion !== false) {
|
||||
$this->assertMatchesRegularExpression(
|
||||
"/^\d+\.\d+\.\d+$/",
|
||||
$latestVersion,
|
||||
"Version should be in format X.Y.Z"
|
||||
);
|
||||
|
||||
// Latest version should be at least v1.3.0 (current latest)
|
||||
$this->assertGreaterThanOrEqual(
|
||||
"1.3.0",
|
||||
$latestVersion,
|
||||
"Latest version should be at least v1.3.0"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test fetching release information from real GitHub repository
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testFetchReleaseInformationFromRealRepo(): void
|
||||
{
|
||||
$config = new UpdaterConfig(
|
||||
self::$testPluginFile,
|
||||
"SilverAssist/silver-assist-post-revalidate"
|
||||
);
|
||||
|
||||
$updater = new Updater($config);
|
||||
|
||||
// Get latest version (makes real HTTP request)
|
||||
$version = $updater->getLatestVersion();
|
||||
|
||||
// Should return string version or false
|
||||
$this->assertTrue(
|
||||
$version === false || is_string($version),
|
||||
"getLatestVersion() should return string or false"
|
||||
);
|
||||
|
||||
// If successful, should have valid version
|
||||
if ($version !== false) {
|
||||
// Verify it's a valid semantic version
|
||||
$this->assertMatchesRegularExpression(
|
||||
"/^\d+\.\d+\.\d+$/",
|
||||
$version,
|
||||
"Version should be in X.Y.Z format"
|
||||
);
|
||||
|
||||
// Check if update is available
|
||||
$config2 = new UpdaterConfig(
|
||||
self::$testPluginFile,
|
||||
"SilverAssist/silver-assist-post-revalidate",
|
||||
[
|
||||
"plugin_version" => "0.0.1", // Old version
|
||||
]
|
||||
);
|
||||
|
||||
$updater2 = new Updater($config2);
|
||||
$hasUpdate = $updater2->isUpdateAvailable();
|
||||
|
||||
$this->assertTrue(
|
||||
$hasUpdate,
|
||||
"Should detect update available when current version is 0.0.1"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test GitHub API response structure
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testGitHubAPIResponseStructure(): void
|
||||
{
|
||||
$config = new UpdaterConfig(
|
||||
self::$testPluginFile,
|
||||
"SilverAssist/silver-assist-post-revalidate"
|
||||
);
|
||||
|
||||
$updater = new Updater($config);
|
||||
|
||||
// Get version to trigger API call
|
||||
$version = $updater->getLatestVersion();
|
||||
|
||||
// Skip if API request failed
|
||||
if ($version === false) {
|
||||
$this->markTestSkipped("GitHub API request failed or rate limited");
|
||||
}
|
||||
|
||||
// Verify version is valid format
|
||||
$this->assertMatchesRegularExpression(
|
||||
"/^\d+\.\d+\.\d+$/",
|
||||
$version,
|
||||
"Version should be in X.Y.Z format"
|
||||
);
|
||||
|
||||
// Version should be at least 1.0.0 (first release)
|
||||
$this->assertGreaterThanOrEqual(
|
||||
"1.0.0",
|
||||
$version,
|
||||
"Version should be at least 1.0.0"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test update check with real GitHub repository
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testUpdateCheckWithRealRepo(): void
|
||||
{
|
||||
$config = new UpdaterConfig(
|
||||
self::$testPluginFile,
|
||||
"SilverAssist/silver-assist-post-revalidate",
|
||||
[
|
||||
"plugin_name" => "Silver Assist Post Revalidate",
|
||||
"plugin_version" => "0.0.1", // Old version to trigger update
|
||||
]
|
||||
);
|
||||
|
||||
$updater = new Updater($config);
|
||||
|
||||
// This makes a real HTTP request to GitHub
|
||||
$hasUpdate = $updater->isUpdateAvailable();
|
||||
|
||||
// Should return boolean
|
||||
$this->assertIsBool($hasUpdate);
|
||||
|
||||
// With version 0.0.1, there should be an update available
|
||||
// (unless API request failed)
|
||||
if ($hasUpdate) {
|
||||
$this->assertTrue(
|
||||
$hasUpdate,
|
||||
"Update should be available for version 0.0.1"
|
||||
);
|
||||
|
||||
// Get latest version to verify
|
||||
$latestVersion = $updater->getLatestVersion();
|
||||
$this->assertNotFalse($latestVersion);
|
||||
$this->assertGreaterThan("0.0.1", $latestVersion);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test caching of GitHub API responses
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testGitHubAPIResponseCaching(): void
|
||||
{
|
||||
$config = new UpdaterConfig(
|
||||
self::$testPluginFile,
|
||||
"SilverAssist/silver-assist-post-revalidate",
|
||||
[
|
||||
"cache_duration" => 3600, // 1 hour
|
||||
]
|
||||
);
|
||||
|
||||
$updater = new Updater($config);
|
||||
|
||||
// First call - should make HTTP request
|
||||
$version1 = $updater->getLatestVersion();
|
||||
|
||||
// Skip if API request failed
|
||||
if ($version1 === false) {
|
||||
$this->markTestSkipped("GitHub API request failed or rate limited");
|
||||
}
|
||||
|
||||
// Second call - should use cache (much faster)
|
||||
$startTime = microtime(true);
|
||||
$version2 = $updater->getLatestVersion();
|
||||
$elapsed = microtime(true) - $startTime;
|
||||
|
||||
// Both should return same version
|
||||
$this->assertEquals($version1, $version2);
|
||||
|
||||
// Second call should be very fast (cached)
|
||||
$this->assertLessThan(
|
||||
0.01, // Less than 10ms
|
||||
$elapsed,
|
||||
"Cached call should be very fast"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test GitHub API rate limiting handling
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testGitHubAPIRateLimitHandling(): void
|
||||
{
|
||||
$config = new UpdaterConfig(
|
||||
self::$testPluginFile,
|
||||
"SilverAssist/silver-assist-post-revalidate",
|
||||
[
|
||||
"cache_duration" => 1, // 1 second cache
|
||||
]
|
||||
);
|
||||
|
||||
$updater = new Updater($config);
|
||||
|
||||
// Make multiple requests (cached after first)
|
||||
$results = [];
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$version = $updater->getLatestVersion();
|
||||
$results[] = $version;
|
||||
}
|
||||
|
||||
// At least one request should succeed (or all should fail gracefully)
|
||||
$hasSuccess = false;
|
||||
foreach ($results as $result) {
|
||||
if ($result !== false) {
|
||||
$hasSuccess = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// All results should be the same (cached)
|
||||
$uniqueResults = array_unique($results);
|
||||
$this->assertCount(
|
||||
1,
|
||||
$uniqueResults,
|
||||
"All requests should return same result (cached)"
|
||||
);
|
||||
|
||||
$this->assertTrue(
|
||||
$hasSuccess || count(array_filter($results)) === 0,
|
||||
"Should either succeed or fail gracefully for all requests"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test asset pattern matching with real releases
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testAssetPatternMatchingWithRealReleases(): void
|
||||
{
|
||||
$config = new UpdaterConfig(
|
||||
self::$testPluginFile,
|
||||
"SilverAssist/silver-assist-post-revalidate",
|
||||
[
|
||||
// Custom asset pattern to match the plugin's naming convention
|
||||
"asset_pattern" => "silver-assist-post-revalidate-v{version}.zip",
|
||||
]
|
||||
);
|
||||
|
||||
$updater = new Updater($config);
|
||||
|
||||
// Get latest version
|
||||
$version = $updater->getLatestVersion();
|
||||
|
||||
// Skip if API request failed
|
||||
if ($version === false) {
|
||||
$this->markTestSkipped("GitHub API request failed or rate limited");
|
||||
}
|
||||
|
||||
// Verify version format
|
||||
$this->assertMatchesRegularExpression(
|
||||
"/^\d+\.\d+\.\d+$/",
|
||||
$version,
|
||||
"Version should be in X.Y.Z format"
|
||||
);
|
||||
|
||||
// Verify the asset pattern is configured correctly
|
||||
$this->assertEquals(
|
||||
"silver-assist-post-revalidate-v{version}.zip",
|
||||
$config->assetPattern,
|
||||
"Asset pattern should be configured"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test version comparison with current and latest versions
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testVersionComparisonWithRealVersions(): void
|
||||
{
|
||||
// Test with old version (should have update)
|
||||
$config1 = new UpdaterConfig(
|
||||
self::$testPluginFile,
|
||||
"SilverAssist/silver-assist-post-revalidate",
|
||||
[
|
||||
"plugin_version" => "1.0.0",
|
||||
]
|
||||
);
|
||||
|
||||
$updater1 = new Updater($config1);
|
||||
$latestVersion = $updater1->getLatestVersion();
|
||||
|
||||
// Skip if API failed
|
||||
if ($latestVersion === false) {
|
||||
$this->markTestSkipped("GitHub API request failed");
|
||||
}
|
||||
|
||||
// 1.0.0 should be older than latest
|
||||
$hasUpdate = $updater1->isUpdateAvailable();
|
||||
$this->assertTrue(
|
||||
$hasUpdate,
|
||||
"Version 1.0.0 should have update available (latest: {$latestVersion})"
|
||||
);
|
||||
|
||||
// Verify version comparison works correctly
|
||||
$this->assertGreaterThan(
|
||||
"1.0.0",
|
||||
$latestVersion,
|
||||
"Latest version should be greater than 1.0.0"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test GitHub repository information retrieval
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function testGitHubRepositoryInformation(): void
|
||||
{
|
||||
$config = new UpdaterConfig(
|
||||
self::$testPluginFile,
|
||||
"SilverAssist/silver-assist-post-revalidate"
|
||||
);
|
||||
|
||||
$updater = new Updater($config);
|
||||
|
||||
// Get version (triggers API call)
|
||||
$version = $updater->getLatestVersion();
|
||||
|
||||
// Skip if failed
|
||||
if ($version === false) {
|
||||
$this->markTestSkipped("GitHub API request failed");
|
||||
}
|
||||
|
||||
// Verify repository info is correct
|
||||
$this->assertEquals(
|
||||
"SilverAssist/silver-assist-post-revalidate",
|
||||
$config->githubRepo,
|
||||
"Repository should be configured correctly"
|
||||
);
|
||||
|
||||
// Verify version is reasonable (>= 1.0.0 and < 100.0.0)
|
||||
$this->assertGreaterThanOrEqual(
|
||||
"1.0.0",
|
||||
$version,
|
||||
"Version should be at least 1.0.0"
|
||||
);
|
||||
|
||||
$this->assertLessThan(
|
||||
"100.0.0",
|
||||
$version,
|
||||
"Version should be less than 100.0.0"
|
||||
);
|
||||
}
|
||||
}
|
||||
142
tests/Integration/UpdaterIntegrationTest.php
Normal file
142
tests/Integration/UpdaterIntegrationTest.php
Normal file
|
|
@ -0,0 +1,142 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Integration tests for Updater class
|
||||
*
|
||||
* @package SilverAssist\WpGithubUpdater\Tests\Integration
|
||||
*/
|
||||
|
||||
namespace SilverAssist\WpGithubUpdater\Tests\Integration;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SilverAssist\WpGithubUpdater\Updater;
|
||||
use SilverAssist\WpGithubUpdater\UpdaterConfig;
|
||||
|
||||
/**
|
||||
* Test Updater integration with GitHub API
|
||||
*
|
||||
* These tests verify the integration between the Updater class
|
||||
* and external dependencies like GitHub API (mocked).
|
||||
*/
|
||||
class UpdaterIntegrationTest extends TestCase
|
||||
{
|
||||
private UpdaterConfig $config;
|
||||
private string $testPluginFile;
|
||||
|
||||
/**
|
||||
* Set up test environment before each test
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Create a temporary plugin file for testing
|
||||
$this->testPluginFile = sys_get_temp_dir() . "/test-plugin.php";
|
||||
file_put_contents($this->testPluginFile, "<?php\n/*\nPlugin Name: Test Plugin\nVersion: 1.0.0\n*/");
|
||||
|
||||
// Create test configuration
|
||||
$this->config = new UpdaterConfig($this->testPluginFile, "SilverAssist/test-repo", [
|
||||
"plugin_name" => "Test Plugin",
|
||||
"plugin_description" => "Test plugin description",
|
||||
"cache_duration" => 60, // Short cache for testing
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after each test
|
||||
*/
|
||||
protected function tearDown(): void
|
||||
{
|
||||
if (file_exists($this->testPluginFile)) {
|
||||
unlink($this->testPluginFile);
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that Updater can be instantiated with valid configuration
|
||||
*/
|
||||
public function testUpdaterInstantiation(): void
|
||||
{
|
||||
$updater = new Updater($this->config);
|
||||
|
||||
$this->assertInstanceOf(Updater::class, $updater);
|
||||
$this->assertEquals("1.0.0", $updater->getCurrentVersion());
|
||||
$this->assertEquals("SilverAssist/test-repo", $updater->getGithubRepo());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test configuration validation
|
||||
*/
|
||||
public function testConfigurationValidation(): void
|
||||
{
|
||||
$this->assertEquals("Test Plugin", $this->config->pluginName);
|
||||
$this->assertEquals("Test plugin description", $this->config->pluginDescription);
|
||||
$this->assertEquals("SilverAssist/test-repo", $this->config->githubRepo);
|
||||
$this->assertEquals(60, $this->config->cacheDuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test custom temporary directory configuration
|
||||
*/
|
||||
public function testCustomTempDirConfiguration(): void
|
||||
{
|
||||
$customTempDir = sys_get_temp_dir() . "/wp-github-updater-test";
|
||||
|
||||
$config = new UpdaterConfig($this->testPluginFile, "SilverAssist/test-repo", [
|
||||
"custom_temp_dir" => $customTempDir,
|
||||
]);
|
||||
|
||||
$this->assertEquals($customTempDir, $config->customTempDir);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test text domain configuration
|
||||
*/
|
||||
public function testTextDomainConfiguration(): void
|
||||
{
|
||||
$config = new UpdaterConfig($this->testPluginFile, "SilverAssist/test-repo", [
|
||||
"text_domain" => "custom-domain",
|
||||
]);
|
||||
|
||||
$this->assertEquals("custom-domain", $config->textDomain);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test AJAX configuration
|
||||
*/
|
||||
public function testAjaxConfiguration(): void
|
||||
{
|
||||
$this->assertNotEmpty($this->config->ajaxAction);
|
||||
$this->assertNotEmpty($this->config->ajaxNonce);
|
||||
$this->assertEquals("check_plugin_version", $this->config->ajaxAction);
|
||||
$this->assertEquals("plugin_version_check", $this->config->ajaxNonce);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test asset pattern configuration
|
||||
*/
|
||||
public function testAssetPatternConfiguration(): void
|
||||
{
|
||||
$config = new UpdaterConfig($this->testPluginFile, "SilverAssist/test-repo", [
|
||||
"asset_pattern" => "custom-{slug}-{version}.zip",
|
||||
]);
|
||||
|
||||
$this->assertEquals("custom-{slug}-{version}.zip", $config->assetPattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test WordPress requirements configuration
|
||||
*/
|
||||
public function testWordPressRequirementsConfiguration(): void
|
||||
{
|
||||
$config = new UpdaterConfig($this->testPluginFile, "SilverAssist/test-repo", [
|
||||
"requires_wordpress" => "6.2",
|
||||
"requires_php" => "8.1",
|
||||
]);
|
||||
|
||||
$this->assertEquals("6.2", $config->requiresWordPress);
|
||||
$this->assertEquals("8.1", $config->requiresPHP);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,20 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace SilverAssist\WpGithubUpdater\Tests;
|
||||
namespace SilverAssist\WpGithubUpdater\Tests\Unit;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SilverAssist\WpGithubUpdater\UpdaterConfig;
|
||||
|
||||
class UpdaterConfigTest extends TestCase
|
||||
{
|
||||
private static string $testPluginFile;
|
||||
|
||||
public static function setUpBeforeClass(): void
|
||||
{
|
||||
self::$testPluginFile = dirname(__DIR__) . "/fixtures/test-plugin.php";
|
||||
}
|
||||
|
||||
public function testBasicConfiguration(): void
|
||||
{
|
||||
$config = new UpdaterConfig("/path/to/plugin.php", "owner/repo");
|
||||
$config = new UpdaterConfig(self::$testPluginFile, "owner/repo");
|
||||
|
||||
$this->assertEquals("/path/to/plugin.php", $config->pluginFile);
|
||||
$this->assertEquals(self::$testPluginFile, $config->pluginFile);
|
||||
$this->assertEquals("owner/repo", $config->githubRepo);
|
||||
$this->assertEquals("6.0", $config->requiresWordPress);
|
||||
$this->assertEquals("8.0", $config->requiresPHP);
|
||||
$this->assertEquals("8.2", $config->requiresPHP);
|
||||
$this->assertEquals("{slug}-v{version}.zip", $config->assetPattern);
|
||||
$this->assertEquals("wp-github-updater", $config->textDomain);
|
||||
}
|
||||
|
|
@ -34,7 +41,7 @@ class UpdaterConfigTest extends TestCase
|
|||
"text_domain" => "my-custom-plugin"
|
||||
];
|
||||
|
||||
$config = new UpdaterConfig("/path/to/plugin.php", "owner/repo", $options);
|
||||
$config = new UpdaterConfig(self::$testPluginFile, "owner/repo", $options);
|
||||
|
||||
$this->assertEquals("Test Plugin", $config->pluginName);
|
||||
$this->assertEquals("A test plugin", $config->pluginDescription);
|
||||
|
|
@ -50,7 +57,7 @@ class UpdaterConfigTest extends TestCase
|
|||
|
||||
public function testTranslationMethods(): void
|
||||
{
|
||||
$config = new UpdaterConfig("/path/to/plugin.php", "owner/repo", [
|
||||
$config = new UpdaterConfig(self::$testPluginFile, "owner/repo", [
|
||||
"text_domain" => "test-domain"
|
||||
]);
|
||||
|
||||
258
tests/WordPress/MockPluginTest.php
Normal file
258
tests/WordPress/MockPluginTest.php
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Tests for Mock Plugin with WordPress Test Suite
|
||||
*
|
||||
* These tests use the WordPress Test Suite to test the updater
|
||||
* in a real WordPress environment with the mock plugin.
|
||||
*
|
||||
* NOTE: These tests are ONLY loaded when WordPress Test Suite is available.
|
||||
* They will be skipped automatically if WP_UnitTestCase is not defined.
|
||||
*
|
||||
* @package SilverAssist\WpGithubUpdater\Tests\WordPress
|
||||
*/
|
||||
|
||||
namespace SilverAssist\WpGithubUpdater\Tests\WordPress;
|
||||
|
||||
// Only load tests if WordPress Test Suite is available
|
||||
if (!class_exists("WP_UnitTestCase")) {
|
||||
return;
|
||||
}
|
||||
|
||||
use WP_UnitTestCase;
|
||||
use SilverAssist\WpGithubUpdater\Updater;
|
||||
|
||||
/**
|
||||
* Mock Plugin Integration Tests
|
||||
*
|
||||
* These tests require WordPress Test Suite to be installed.
|
||||
* Run: ./bin/install-wp-tests.sh wordpress_test root '' localhost 6.7.1
|
||||
*/
|
||||
class MockPluginTest extends WP_UnitTestCase
|
||||
{
|
||||
private string $pluginFile;
|
||||
private ?Updater $updater = null;
|
||||
|
||||
/**
|
||||
* Set up test environment
|
||||
*/
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Define plugin file path
|
||||
$this->pluginFile = dirname(__DIR__) . "/fixtures/mock-plugin/mock-plugin.php";
|
||||
|
||||
// Load the mock plugin
|
||||
if (file_exists($this->pluginFile)) {
|
||||
require_once $this->pluginFile;
|
||||
|
||||
// Initialize the updater
|
||||
do_action("plugins_loaded");
|
||||
|
||||
// Get updater instance
|
||||
$this->updater = mock_plugin_get_updater();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that mock plugin file exists
|
||||
*/
|
||||
public function testMockPluginFileExists(): void
|
||||
{
|
||||
$this->assertFileExists($this->pluginFile, "Mock plugin file should exist");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that mock plugin can be loaded
|
||||
*/
|
||||
public function testMockPluginCanBeLoaded(): void
|
||||
{
|
||||
$this->assertTrue(
|
||||
function_exists("mock_plugin_init_updater"),
|
||||
"Mock plugin functions should be available"
|
||||
);
|
||||
$this->assertTrue(
|
||||
function_exists("mock_plugin_get_updater"),
|
||||
"Mock plugin helper functions should be available"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test that updater is initialized
|
||||
*/
|
||||
public function testUpdaterIsInitialized(): void
|
||||
{
|
||||
$this->assertNotNull($this->updater, "Updater should be initialized");
|
||||
$this->assertInstanceOf(Updater::class, $this->updater, "Should be Updater instance");
|
||||
}
|
||||
|
||||
/**
|
||||
* Test updater configuration
|
||||
*/
|
||||
public function testUpdaterConfiguration(): void
|
||||
{
|
||||
if (!$this->updater) {
|
||||
$this->markTestSkipped("Updater not initialized");
|
||||
}
|
||||
|
||||
$this->assertEquals("SilverAssist/mock-test-repo", $this->updater->getGithubRepo());
|
||||
$this->assertEquals("1.0.0", $this->updater->getCurrentVersion());
|
||||
}
|
||||
|
||||
/**
|
||||
* Test WordPress hooks are registered
|
||||
*/
|
||||
public function testWordPressHooksAreRegistered(): void
|
||||
{
|
||||
// Check that update check filter is registered
|
||||
$this->assertNotFalse(
|
||||
has_filter("pre_set_site_transient_update_plugins"),
|
||||
"Update check filter should be registered"
|
||||
);
|
||||
|
||||
// Check that plugin info filter is registered
|
||||
$this->assertNotFalse(
|
||||
has_filter("plugins_api"),
|
||||
"Plugin info filter should be registered"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test AJAX actions are registered
|
||||
*/
|
||||
public function testAjaxActionsAreRegistered(): void
|
||||
{
|
||||
// Check that AJAX actions are registered (only for logged-in users)
|
||||
$this->assertNotFalse(
|
||||
has_action("wp_ajax_mock_plugin_check_version"),
|
||||
"AJAX check version action should be registered"
|
||||
);
|
||||
|
||||
// Check that dismiss notice action is registered
|
||||
$this->assertNotFalse(
|
||||
has_action("wp_ajax_mock_plugin_check_version_dismiss_notice"),
|
||||
"AJAX dismiss notice action should be registered"
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test plugin activation
|
||||
*/
|
||||
public function testPluginActivation(): void
|
||||
{
|
||||
// Set a transient to test cleanup
|
||||
set_transient("mock-plugin_version_check", "test_data", 3600);
|
||||
$this->assertNotFalse(get_transient("mock-plugin_version_check"));
|
||||
|
||||
// Trigger activation
|
||||
do_action("activate_" . plugin_basename($this->pluginFile));
|
||||
|
||||
// In real implementation, transient should be cleared
|
||||
// For now, just verify activation doesn't cause errors
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test plugin deactivation
|
||||
*/
|
||||
public function testPluginDeactivation(): void
|
||||
{
|
||||
// Set a transient
|
||||
set_transient("mock-plugin_version_check", "test_data", 3600);
|
||||
|
||||
// Trigger deactivation
|
||||
do_action("deactivate_" . plugin_basename($this->pluginFile));
|
||||
|
||||
// Verify cleanup happens (implementation-specific)
|
||||
$this->assertTrue(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test admin menu is registered
|
||||
*/
|
||||
public function testAdminMenuIsRegistered(): void
|
||||
{
|
||||
// Set current user as administrator
|
||||
$user_id = $this->factory->user->create(["role" => "administrator"]);
|
||||
wp_set_current_user($user_id);
|
||||
|
||||
// Trigger admin menu hook
|
||||
do_action("admin_menu");
|
||||
|
||||
// Check that menu was added
|
||||
global $menu;
|
||||
$this->assertIsArray($menu);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test update check with transient caching
|
||||
*/
|
||||
public function testUpdateCheckWithCaching(): void
|
||||
{
|
||||
if (!$this->updater) {
|
||||
$this->markTestSkipped("Updater not initialized");
|
||||
}
|
||||
|
||||
// Clear any existing cache
|
||||
delete_transient("mock-plugin_version_check");
|
||||
|
||||
// First check should query API (we can't test actual API in unit tests)
|
||||
$updateAvailable = $this->updater->isUpdateAvailable();
|
||||
$this->assertIsBool($updateAvailable);
|
||||
|
||||
// Second check should use cache (if API was successful)
|
||||
$updateAvailable2 = $this->updater->isUpdateAvailable();
|
||||
$this->assertIsBool($updateAvailable2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test plugin data retrieval
|
||||
*/
|
||||
public function testPluginDataRetrieval(): void
|
||||
{
|
||||
$pluginData = \get_plugin_data($this->pluginFile, false, false); // Don't markup, don't translate
|
||||
|
||||
$this->assertIsArray($pluginData);
|
||||
$this->assertEquals("Mock Plugin for WP GitHub Updater Tests", $pluginData["Name"]);
|
||||
$this->assertEquals("1.0.0", $pluginData["Version"]);
|
||||
$this->assertEquals("SilverAssist", $pluginData["Author"]);
|
||||
$this->assertEquals("6.0", $pluginData["RequiresWP"]);
|
||||
$this->assertEquals("8.2", $pluginData["RequiresPHP"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test plugin basename generation
|
||||
*/
|
||||
public function testPluginBasename(): void
|
||||
{
|
||||
$basename = plugin_basename($this->pluginFile);
|
||||
|
||||
$this->assertIsString($basename);
|
||||
$this->assertStringContainsString("mock-plugin.php", $basename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test custom temporary directory configuration
|
||||
*/
|
||||
public function testCustomTempDirectoryConfiguration(): void
|
||||
{
|
||||
// This tests the v1.1.3+ feature for custom temp directories
|
||||
$expectedTempDir = WP_CONTENT_DIR . "/uploads/temp";
|
||||
|
||||
// The mock plugin configures a custom temp dir
|
||||
// We verify this through the configuration
|
||||
$this->assertDirectoryExists(WP_CONTENT_DIR . "/uploads");
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after tests
|
||||
*/
|
||||
public function tearDown(): void
|
||||
{
|
||||
// Clean up transients
|
||||
delete_transient("mock-plugin_version_check");
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
}
|
||||
213
tests/WordPress/WordPressHooksTest.php
Normal file
213
tests/WordPress/WordPressHooksTest.php
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* WordPress-specific integration tests
|
||||
*
|
||||
* @package SilverAssist\WpGithubUpdater\Tests\WordPress
|
||||
*/
|
||||
|
||||
namespace SilverAssist\WpGithubUpdater\Tests\WordPress;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use SilverAssist\WpGithubUpdater\Updater;
|
||||
use SilverAssist\WpGithubUpdater\UpdaterConfig;
|
||||
|
||||
/**
|
||||
* Test WordPress-specific functionality
|
||||
*
|
||||
* These tests verify WordPress hooks, filters, and integration points.
|
||||
* Note: These tests use mocked WordPress functions.
|
||||
*/
|
||||
class WordPressHooksTest extends TestCase
|
||||
{
|
||||
private UpdaterConfig $config;
|
||||
private string $testPluginFile;
|
||||
|
||||
/**
|
||||
* Set up test environment before each test
|
||||
*/
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
// Create a temporary plugin file for testing
|
||||
$this->testPluginFile = sys_get_temp_dir() . "/test-plugin.php";
|
||||
file_put_contents(
|
||||
$this->testPluginFile,
|
||||
"<?php\n/*\nPlugin Name: Test Plugin\nVersion: 1.0.0\nDescription: Test plugin\n*/"
|
||||
);
|
||||
|
||||
// Create test configuration
|
||||
$this->config = new UpdaterConfig($this->testPluginFile, "SilverAssist/test-repo", [
|
||||
"plugin_name" => "Test Plugin",
|
||||
"plugin_description" => "Test plugin description",
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up after each test
|
||||
*/
|
||||
protected function tearDown(): void
|
||||
{
|
||||
if (file_exists($this->testPluginFile)) {
|
||||
unlink($this->testPluginFile);
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
/**
|
||||
* Test configuration object creation
|
||||
*/
|
||||
public function testConfigurationCreation(): void
|
||||
{
|
||||
$this->assertInstanceOf(UpdaterConfig::class, $this->config);
|
||||
$this->assertEquals("SilverAssist/test-repo", $this->config->githubRepo);
|
||||
$this->assertEquals($this->testPluginFile, $this->config->pluginFile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test plugin homepage configuration
|
||||
*/
|
||||
public function testPluginHomepage(): void
|
||||
{
|
||||
$expectedHomepage = "https://github.com/SilverAssist/test-repo";
|
||||
$this->assertEquals($expectedHomepage, $this->config->pluginHomepage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test plugin author configuration
|
||||
*/
|
||||
public function testPluginAuthor(): void
|
||||
{
|
||||
// When plugin file doesn't exist or can't be read, author will be empty string
|
||||
// unless explicitly provided in options
|
||||
$this->assertIsString($this->config->pluginAuthor);
|
||||
|
||||
// Test with explicit author option
|
||||
$configWithAuthor = new UpdaterConfig($this->testPluginFile, "SilverAssist/test-repo", [
|
||||
"plugin_author" => "SilverAssist",
|
||||
]);
|
||||
$this->assertEquals("SilverAssist", $configWithAuthor->pluginAuthor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test cache duration configuration
|
||||
*/
|
||||
public function testCacheDuration(): void
|
||||
{
|
||||
$this->assertEquals(43200, $this->config->cacheDuration); // Default 12 hours
|
||||
|
||||
$customConfig = new UpdaterConfig($this->testPluginFile, "SilverAssist/test-repo", [
|
||||
"cache_duration" => 3600,
|
||||
]);
|
||||
$this->assertEquals(3600, $customConfig->cacheDuration);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test transient naming convention
|
||||
*/
|
||||
public function testTransientNaming(): void
|
||||
{
|
||||
$pluginBasename = basename(dirname($this->testPluginFile));
|
||||
$expectedTransient = "{$pluginBasename}_version_check";
|
||||
|
||||
// The transient name should follow WordPress conventions (lowercase, numbers, underscores, dashes)
|
||||
// Note: basename() may return uppercase letters, which is acceptable in WordPress transients
|
||||
$this->assertMatchesRegularExpression("/^[a-zA-Z0-9_-]+$/", $expectedTransient);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test AJAX action naming convention
|
||||
*/
|
||||
public function testAjaxActionNaming(): void
|
||||
{
|
||||
$ajaxAction = $this->config->ajaxAction;
|
||||
|
||||
// AJAX action should follow WordPress conventions (lowercase, numbers, underscores)
|
||||
$this->assertNotEmpty($ajaxAction);
|
||||
$this->assertMatchesRegularExpression("/^[a-z0-9_-]+$/", $ajaxAction);
|
||||
$this->assertEquals("check_plugin_version", $ajaxAction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test nonce naming convention
|
||||
*/
|
||||
public function testNonceNaming(): void
|
||||
{
|
||||
$nonce = $this->config->ajaxNonce;
|
||||
|
||||
// Nonce should follow WordPress conventions (lowercase, numbers, underscores)
|
||||
$this->assertNotEmpty($nonce);
|
||||
$this->assertMatchesRegularExpression("/^[a-z0-9_-]+$/", $nonce);
|
||||
$this->assertEquals("plugin_version_check", $nonce);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test plugin data structure
|
||||
*/
|
||||
public function testPluginDataStructure(): void
|
||||
{
|
||||
$this->assertIsString($this->config->pluginName);
|
||||
$this->assertIsString($this->config->pluginDescription);
|
||||
$this->assertIsString($this->config->pluginAuthor);
|
||||
$this->assertIsString($this->config->pluginHomepage);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test WordPress version requirements
|
||||
*/
|
||||
public function testWordPressVersionRequirements(): void
|
||||
{
|
||||
$this->assertIsString($this->config->requiresWordPress);
|
||||
$this->assertMatchesRegularExpression("/^\d+\.\d+$/", $this->config->requiresWordPress);
|
||||
$this->assertGreaterThanOrEqual(6.0, (float) $this->config->requiresWordPress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test PHP version requirements
|
||||
*/
|
||||
public function testPHPVersionRequirements(): void
|
||||
{
|
||||
$this->assertIsString($this->config->requiresPHP);
|
||||
$this->assertMatchesRegularExpression("/^\d+\.\d+$/", $this->config->requiresPHP);
|
||||
$this->assertGreaterThanOrEqual(8.2, (float) $this->config->requiresPHP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test asset pattern replacement tokens
|
||||
*/
|
||||
public function testAssetPatternTokens(): void
|
||||
{
|
||||
$pattern = $this->config->assetPattern;
|
||||
|
||||
// Pattern should contain replacement tokens
|
||||
$this->assertStringContainsString("{slug}", $pattern);
|
||||
$this->assertStringContainsString("{version}", $pattern);
|
||||
$this->assertStringEndsWith(".zip", $pattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test translation function wrapper
|
||||
*/
|
||||
public function testTranslationFunctionWrapper(): void
|
||||
{
|
||||
$testString = "Test string";
|
||||
$translated = $this->config->__($testString);
|
||||
|
||||
// In test environment, should return the original string
|
||||
$this->assertEquals($testString, $translated);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test GitHub API URL construction
|
||||
*/
|
||||
public function testGitHubApiUrlConstruction(): void
|
||||
{
|
||||
$repo = $this->config->githubRepo;
|
||||
$expectedBaseUrl = "https://api.github.com/repos/{$repo}";
|
||||
|
||||
$this->assertStringContainsString("SilverAssist", $expectedBaseUrl);
|
||||
$this->assertStringContainsString("test-repo", $expectedBaseUrl);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,28 +1,117 @@
|
|||
<?php
|
||||
|
||||
// Bootstrap file for PHPUnit tests
|
||||
// This file contains mock WordPress functions for testing
|
||||
/**
|
||||
* PHPUnit bootstrap file
|
||||
*
|
||||
* This bootstrap automatically detects the test environment:
|
||||
* - If WordPress Test Suite is available, loads it for WordPress integration tests
|
||||
* - Otherwise, uses mocked WordPress functions for unit/integration tests
|
||||
*
|
||||
* @package SilverAssist\WpGithubUpdater\Tests
|
||||
*/
|
||||
|
||||
if (!function_exists('__')) {
|
||||
function __($text, $domain = 'default')
|
||||
{
|
||||
return $text;
|
||||
}
|
||||
// Determine if we should load WordPress Test Suite
|
||||
$_tests_dir = getenv("WP_TESTS_DIR");
|
||||
$_skip_wp_tests = filter_var(getenv("SKIP_WP_TESTS_IF_MISSING"), FILTER_VALIDATE_BOOLEAN);
|
||||
|
||||
// If not set via environment, try common locations
|
||||
if (!$_tests_dir) {
|
||||
$_tests_dir = rtrim(sys_get_temp_dir(), "/\\") . "/wordpress-tests-lib";
|
||||
}
|
||||
|
||||
if (!function_exists('esc_html__')) {
|
||||
function esc_html__($text, $domain = 'default')
|
||||
{
|
||||
return htmlspecialchars($text, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists('get_plugin_data')) {
|
||||
function get_plugin_data($file)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
// Check if WordPress Test Suite is available
|
||||
$_wp_tests_available = file_exists($_tests_dir . "/includes/functions.php");
|
||||
|
||||
// Load Composer autoloader
|
||||
require_once __DIR__ . '/../vendor/autoload.php';
|
||||
require_once __DIR__ . "/../vendor/autoload.php";
|
||||
|
||||
// Load Yoast PHPUnit Polyfills for PHPUnit 9.x compatibility
|
||||
if (file_exists(__DIR__ . "/../vendor/yoast/phpunit-polyfills/phpunitpolyfills-autoload.php")) {
|
||||
require_once __DIR__ . "/../vendor/yoast/phpunit-polyfills/phpunitpolyfills-autoload.php";
|
||||
}
|
||||
|
||||
// Decide which environment to load
|
||||
if ($_wp_tests_available) {
|
||||
// WordPress Test Suite is available - load it
|
||||
echo "\n";
|
||||
echo "====================================\n";
|
||||
echo "WP GitHub Updater Test Suite\n";
|
||||
echo "====================================\n";
|
||||
echo "Mode: WordPress Integration Tests\n";
|
||||
echo "WP Tests Dir: $_tests_dir\n";
|
||||
echo "====================================\n\n";
|
||||
|
||||
// Load WordPress test suite
|
||||
require_once $_tests_dir . "/includes/functions.php";
|
||||
|
||||
/**
|
||||
* Manually load the mock plugin for testing
|
||||
*
|
||||
* This loads our mock plugin that integrates the WP GitHub Updater package
|
||||
* into a WordPress environment for real integration testing.
|
||||
*/
|
||||
function _manually_load_plugin()
|
||||
{
|
||||
// Load the mock plugin that uses the WP GitHub Updater package
|
||||
$mock_plugin_file = __DIR__ . "/fixtures/mock-plugin/mock-plugin.php";
|
||||
|
||||
if (file_exists($mock_plugin_file)) {
|
||||
require_once $mock_plugin_file;
|
||||
echo "✓ Mock plugin loaded: {$mock_plugin_file}\n";
|
||||
} else {
|
||||
echo "⚠️ Mock plugin not found at: {$mock_plugin_file}\n";
|
||||
}
|
||||
}
|
||||
|
||||
tests_add_filter("muplugins_loaded", "_manually_load_plugin");
|
||||
|
||||
// Start up the WP testing environment
|
||||
require $_tests_dir . "/includes/bootstrap.php";
|
||||
} else {
|
||||
// WordPress Test Suite not available - use mocks
|
||||
|
||||
// Define WordPress constants for mock environment
|
||||
if (!defined("ABSPATH")) {
|
||||
define("ABSPATH", __DIR__ . "/../");
|
||||
}
|
||||
|
||||
if (!defined("WP_CONTENT_DIR")) {
|
||||
define("WP_CONTENT_DIR", ABSPATH . "wp-content");
|
||||
}
|
||||
|
||||
if (!defined("WP_PLUGIN_DIR")) {
|
||||
define("WP_PLUGIN_DIR", WP_CONTENT_DIR . "/plugins");
|
||||
}
|
||||
|
||||
// Load WordPress function mocks for non-WP-Test-Suite environment
|
||||
require_once __DIR__ . "/wordpress-mocks.php";
|
||||
|
||||
if (!$_skip_wp_tests) {
|
||||
echo "\n";
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
|
||||
echo "⚠️ WordPress Test Suite not found\n";
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
|
||||
echo "\n";
|
||||
echo "Running tests with mocked WordPress functions.\n";
|
||||
echo "For full WordPress integration tests, install WordPress Test Suite:\n";
|
||||
echo " ./bin/install-wp-tests.sh wordpress_test root '' localhost 6.7.1\n";
|
||||
echo "\n";
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n";
|
||||
echo "\n";
|
||||
}
|
||||
|
||||
// Note: We don't load wordpress-stubs here because they conflict with our mocks
|
||||
// wordpress-stubs are only used for static analysis (PHPStan)
|
||||
// Our WordPress function mocks are loaded via Composer's autoload-dev files
|
||||
// See composer.json autoload-dev.files section
|
||||
|
||||
// Display test suite information
|
||||
echo "\n";
|
||||
echo "====================================\n";
|
||||
echo "WP GitHub Updater Test Suite\n";
|
||||
echo "====================================\n";
|
||||
echo "Mode: Unit/Integration Tests (Mocked)\n";
|
||||
echo "PHP Version: " . PHP_VERSION . "\n";
|
||||
echo "PHPUnit Version: " . PHPUnit\Runner\Version::id() . "\n";
|
||||
echo "====================================\n\n";
|
||||
}
|
||||
|
|
|
|||
260
tests/fixtures/mock-plugin/README.md
vendored
Normal file
260
tests/fixtures/mock-plugin/README.md
vendored
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
# Mock Plugin for WP GitHub Updater Tests
|
||||
|
||||
## 📋 Descripción
|
||||
|
||||
Este es un **plugin de prueba (fixture)** que demuestra la integración correcta del paquete `silverassist/wp-github-updater` en un entorno real de WordPress.
|
||||
|
||||
## 🎯 Propósito
|
||||
|
||||
El mock plugin sirve para:
|
||||
|
||||
1. **Testing Real de WordPress**: Permite probar el updater en un entorno WordPress completo con WordPress Test Suite
|
||||
2. **Ejemplo de Integración**: Demuestra el patrón recomendado de integración del paquete
|
||||
3. **Documentación Viva**: Sirve como referencia para desarrolladores que integren el paquete
|
||||
|
||||
## 📁 Estructura
|
||||
|
||||
```
|
||||
tests/fixtures/mock-plugin/
|
||||
├── mock-plugin.php # Plugin principal con integración completa
|
||||
├── readme.txt # WordPress plugin readme
|
||||
└── README.md # Esta documentación
|
||||
```
|
||||
|
||||
## 🔧 Características Implementadas
|
||||
|
||||
### ✅ Integración Completa del Updater
|
||||
|
||||
```php
|
||||
// Configuración completa con todas las opciones
|
||||
$config = new UpdaterConfig(
|
||||
__FILE__,
|
||||
"SilverAssist/mock-test-repo",
|
||||
[
|
||||
"plugin_name" => "Mock Plugin for Testing",
|
||||
"cache_duration" => 300, // 5 minutos para testing
|
||||
"text_domain" => "mock-plugin",
|
||||
"custom_temp_dir" => WP_CONTENT_DIR . "/uploads/temp",
|
||||
// ... más opciones
|
||||
]
|
||||
);
|
||||
|
||||
$updater = new Updater($config);
|
||||
```
|
||||
|
||||
### ✅ Panel de Administración
|
||||
|
||||
- Página de admin con información del plugin
|
||||
- Botón para check manual de actualizaciones
|
||||
- Display de versión actual y disponible
|
||||
- Integración AJAX para checks en tiempo real
|
||||
|
||||
### ✅ Hooks de WordPress
|
||||
|
||||
- `plugins_loaded`: Inicialización del updater
|
||||
- `admin_menu`: Menú de administración
|
||||
- `activate_*`: Limpieza de cache en activación
|
||||
- `deactivate_*`: Limpieza de cache en desactivación
|
||||
|
||||
### ✅ AJAX Handlers
|
||||
|
||||
- `mock_plugin_check_version`: Check manual de versión
|
||||
- Verificación de nonces y capabilities
|
||||
- Respuestas JSON formateadas
|
||||
|
||||
## 🧪 Uso en Tests
|
||||
|
||||
### Cargar el Mock Plugin
|
||||
|
||||
El mock plugin se carga automáticamente en el bootstrap de WordPress Test Suite:
|
||||
|
||||
```php
|
||||
// En tests/bootstrap.php
|
||||
function _manually_load_plugin() {
|
||||
require_once __DIR__ . "/fixtures/mock-plugin/mock-plugin.php";
|
||||
}
|
||||
tests_add_filter("muplugins_loaded", "_manually_load_plugin");
|
||||
```
|
||||
|
||||
### Acceder al Updater en Tests
|
||||
|
||||
```php
|
||||
class MyTest extends WP_UnitTestCase {
|
||||
public function testUpdater() {
|
||||
// Obtener instancia del updater
|
||||
$updater = mock_plugin_get_updater();
|
||||
|
||||
// Verificar funcionalidad
|
||||
$this->assertInstanceOf(Updater::class, $updater);
|
||||
$this->assertEquals("1.0.0", $updater->getCurrentVersion());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Ejemplo de Test Completo
|
||||
|
||||
Ver `tests/WordPress/MockPluginTest.php` para ejemplos completos de:
|
||||
|
||||
- ✅ Test de inicialización
|
||||
- ✅ Test de hooks registrados
|
||||
- ✅ Test de AJAX actions
|
||||
- ✅ Test de activación/desactivación
|
||||
- ✅ Test de menu de admin
|
||||
- ✅ Test de caching con transients
|
||||
|
||||
## 📊 Tests Incluidos
|
||||
|
||||
El mock plugin tiene su propia suite de tests en `tests/WordPress/MockPluginTest.php`:
|
||||
|
||||
| Test | Descripción |
|
||||
|------|-------------|
|
||||
| `testMockPluginFileExists` | Verifica que el archivo existe |
|
||||
| `testMockPluginCanBeLoaded` | Verifica que se puede cargar |
|
||||
| `testUpdaterIsInitialized` | Verifica inicialización del updater |
|
||||
| `testUpdaterConfiguration` | Verifica configuración correcta |
|
||||
| `testWordPressHooksAreRegistered` | Verifica hooks de WordPress |
|
||||
| `testAjaxActionsAreRegistered` | Verifica AJAX actions |
|
||||
| `testPluginActivation` | Verifica activación del plugin |
|
||||
| `testPluginDeactivation` | Verifica desactivación del plugin |
|
||||
| `testAdminMenuIsRegistered` | Verifica menú de admin |
|
||||
| `testUpdateCheckWithCaching` | Verifica caching con transients |
|
||||
| `testPluginDataRetrieval` | Verifica lectura de metadata |
|
||||
| `testPluginBasename` | Verifica plugin_basename() |
|
||||
| `testCustomTempDirectoryConfiguration` | Verifica directorio temporal (v1.1.3+) |
|
||||
|
||||
## 🚀 Ejecutar Tests con WordPress Test Suite
|
||||
|
||||
### 1. Instalar WordPress Test Suite
|
||||
|
||||
```bash
|
||||
./scripts/install-wp-tests.sh wordpress_test root '' localhost 6.7.1
|
||||
```
|
||||
|
||||
### 2. Ejecutar Tests de WordPress
|
||||
|
||||
```bash
|
||||
# Todos los tests de WordPress (incluye mock plugin)
|
||||
./vendor/bin/phpunit --testsuite=wordpress
|
||||
|
||||
# Solo tests del mock plugin
|
||||
./vendor/bin/phpunit tests/WordPress/MockPluginTest.php
|
||||
|
||||
# Todos los tests (incluye modo WordPress si está instalado)
|
||||
./vendor/bin/phpunit
|
||||
```
|
||||
|
||||
### 3. Verificar Salida
|
||||
|
||||
Cuando WordPress Test Suite está disponible, verás:
|
||||
|
||||
```
|
||||
====================================
|
||||
WP GitHub Updater Test Suite
|
||||
====================================
|
||||
Mode: WordPress Integration Tests
|
||||
WP Tests Dir: /tmp/wordpress-tests-lib
|
||||
====================================
|
||||
✓ Mock plugin loaded: /path/to/tests/fixtures/mock-plugin/mock-plugin.php
|
||||
```
|
||||
|
||||
## 🔍 Funcionalidades para Testing
|
||||
|
||||
### Metadata del Plugin
|
||||
|
||||
```php
|
||||
$pluginData = get_plugin_data($pluginFile);
|
||||
|
||||
// Retorna:
|
||||
[
|
||||
"Name" => "Mock Plugin for WP GitHub Updater Tests",
|
||||
"Version" => "1.0.0",
|
||||
"Author" => "SilverAssist",
|
||||
"RequiresWP" => "6.0",
|
||||
"RequiresPHP" => "8.2",
|
||||
// ...
|
||||
]
|
||||
```
|
||||
|
||||
### Acceso Global al Updater
|
||||
|
||||
```php
|
||||
// Obtener updater desde cualquier parte
|
||||
$updater = mock_plugin_get_updater();
|
||||
|
||||
// O desde global
|
||||
$updater = $GLOBALS["mock_plugin_updater"];
|
||||
```
|
||||
|
||||
### Limpieza de Cache
|
||||
|
||||
```php
|
||||
// Limpiar cache de versiones
|
||||
delete_transient("mock-plugin_version_check");
|
||||
|
||||
// O usar función de activación
|
||||
do_action("activate_mock-plugin/mock-plugin.php");
|
||||
```
|
||||
|
||||
## 📝 Configuración
|
||||
|
||||
### Opciones Configurables
|
||||
|
||||
El mock plugin demuestra todas las opciones disponibles:
|
||||
|
||||
```php
|
||||
[
|
||||
"plugin_name" => "Mock Plugin for Testing",
|
||||
"plugin_description" => "A mock plugin for WP GitHub Updater tests",
|
||||
"plugin_author" => "SilverAssist",
|
||||
"cache_duration" => 300, // 5 minutos
|
||||
"text_domain" => "mock-plugin",
|
||||
"custom_temp_dir" => WP_CONTENT_DIR . "/uploads/temp",
|
||||
"ajax_action" => "mock_plugin_check_version",
|
||||
"ajax_nonce" => "mock_plugin_nonce",
|
||||
"asset_pattern" => "mock-plugin-{version}.zip",
|
||||
"requires_wordpress" => "6.0",
|
||||
"requires_php" => "8.2",
|
||||
]
|
||||
```
|
||||
|
||||
## ⚠️ Notas Importantes
|
||||
|
||||
### No Usar en Producción
|
||||
|
||||
Este plugin es **exclusivamente para testing** y no debe usarse en sitios de producción:
|
||||
|
||||
- Usa un repositorio GitHub ficticio (`mock-test-repo`)
|
||||
- Cache duration muy corta (5 minutos)
|
||||
- Configuración optimizada para testing
|
||||
|
||||
### Repositorio Ficticio
|
||||
|
||||
El plugin apunta a `SilverAssist/mock-test-repo` que puede no existir. Para tests reales de API, deberás:
|
||||
|
||||
1. Crear un repositorio de prueba en GitHub
|
||||
2. Actualizar la configuración en `mock-plugin.php`
|
||||
3. Crear releases de prueba en ese repositorio
|
||||
|
||||
### Compatibilidad
|
||||
|
||||
- **WordPress**: 6.0+
|
||||
- **PHP**: 8.2+
|
||||
- **PHPUnit**: 9.6+
|
||||
- **WordPress Test Suite**: Requerido para tests completos
|
||||
|
||||
## 🔗 Referencias
|
||||
|
||||
- [Ejemplo de Integración](../../../examples/integration-guide.php)
|
||||
- [Documentación del Paquete](../../../README.md)
|
||||
- [Testing Summary](../../../docs/TESTING-SUMMARY.md)
|
||||
- [WordPress Plugin Unit Tests](https://make.wordpress.org/cli/handbook/misc/plugin-unit-tests/)
|
||||
|
||||
## 📄 Licencia
|
||||
|
||||
MIT - Solo para propósitos de testing
|
||||
|
||||
---
|
||||
|
||||
**Última actualización:** 2025-01-10
|
||||
**Versión:** 1.0.0
|
||||
**Paquete:** silverassist/wp-github-updater v1.1.5
|
||||
213
tests/fixtures/mock-plugin/mock-plugin.php
vendored
Normal file
213
tests/fixtures/mock-plugin/mock-plugin.php
vendored
Normal file
|
|
@ -0,0 +1,213 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Plugin Name: Mock Plugin for WP GitHub Updater Tests
|
||||
* Plugin URI: https://github.com/SilverAssist/wp-github-updater
|
||||
* Description: A mock WordPress plugin used for testing the WP GitHub Updater package
|
||||
* Version: 1.0.0
|
||||
* Author: SilverAssist
|
||||
* Author URI: https://github.com/SilverAssist
|
||||
* License: MIT
|
||||
* Text Domain: mock-plugin
|
||||
* Requires at least: 6.0
|
||||
* Requires PHP: 8.2
|
||||
*
|
||||
* This is a test fixture plugin that demonstrates integration with
|
||||
* the SilverAssist/WpGithubUpdater package for automated testing.
|
||||
*/
|
||||
|
||||
// Prevent direct access
|
||||
if (!defined("ABSPATH")) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Autoload Composer dependencies
|
||||
if (file_exists(__DIR__ . "/../../../vendor/autoload.php")) {
|
||||
require_once __DIR__ . "/../../../vendor/autoload.php";
|
||||
}
|
||||
|
||||
use SilverAssist\WpGithubUpdater\UpdaterConfig;
|
||||
use SilverAssist\WpGithubUpdater\Updater;
|
||||
|
||||
/**
|
||||
* Initialize the GitHub Updater for this mock plugin
|
||||
*
|
||||
* This function demonstrates the recommended integration pattern
|
||||
* for the WP GitHub Updater package.
|
||||
*/
|
||||
function mock_plugin_init_updater(): void
|
||||
{
|
||||
// Create updater configuration
|
||||
$config = new UpdaterConfig(
|
||||
__FILE__, // Plugin main file
|
||||
"SilverAssist/mock-test-repo", // GitHub repository
|
||||
[
|
||||
// Optional: Override plugin metadata
|
||||
"plugin_name" => "Mock Plugin for Testing",
|
||||
"plugin_description" => "A mock plugin for WP GitHub Updater tests",
|
||||
"plugin_author" => "SilverAssist",
|
||||
|
||||
// Optional: Custom cache duration (default: 12 hours)
|
||||
"cache_duration" => 300, // 5 minutes for testing
|
||||
|
||||
// Optional: Custom text domain for translations
|
||||
"text_domain" => "mock-plugin",
|
||||
|
||||
// Optional: Custom temporary directory for downloads
|
||||
"custom_temp_dir" => WP_CONTENT_DIR . "/uploads/temp",
|
||||
|
||||
// Optional: Custom AJAX action names
|
||||
"ajax_action" => "mock_plugin_check_version",
|
||||
"ajax_nonce" => "mock_plugin_nonce",
|
||||
|
||||
// Optional: Custom asset pattern for GitHub releases
|
||||
"asset_pattern" => "mock-plugin-{version}.zip",
|
||||
|
||||
// Optional: WordPress and PHP requirements
|
||||
"requires_wp" => "6.0",
|
||||
"requires_php" => "8.2",
|
||||
"last_updated" => \gmdate("Y-m-d H:i:s"),
|
||||
]
|
||||
);
|
||||
|
||||
// Initialize the updater
|
||||
$updater = new Updater($config);
|
||||
|
||||
// Store in global scope for testing access
|
||||
$GLOBALS["mock_plugin_updater"] = $updater;
|
||||
}
|
||||
|
||||
// Initialize on plugins_loaded hook
|
||||
add_action("plugins_loaded", "mock_plugin_init_updater");
|
||||
|
||||
/**
|
||||
* Add admin menu for testing
|
||||
*/
|
||||
function mock_plugin_admin_menu(): void
|
||||
{
|
||||
add_menu_page(
|
||||
"Mock Plugin",
|
||||
"Mock Plugin",
|
||||
"manage_options",
|
||||
"mock-plugin",
|
||||
"mock_plugin_admin_page",
|
||||
"dashicons-admin-plugins",
|
||||
100
|
||||
);
|
||||
}
|
||||
add_action("admin_menu", "mock_plugin_admin_menu");
|
||||
|
||||
/**
|
||||
* Admin page for testing
|
||||
*/
|
||||
function mock_plugin_admin_page(): void
|
||||
{
|
||||
if (!current_user_can("manage_options")) {
|
||||
return;
|
||||
}
|
||||
|
||||
$updater = $GLOBALS["mock_plugin_updater"] ?? null;
|
||||
|
||||
?>
|
||||
<div class="wrap">
|
||||
<h1><?php echo esc_html(get_admin_page_title()); ?></h1>
|
||||
|
||||
<div class="card">
|
||||
<h2>Plugin Information</h2>
|
||||
<table class="form-table">
|
||||
<tr>
|
||||
<th>Current Version:</th>
|
||||
<td><?php echo esc_html($updater ? $updater->getCurrentVersion() : "N/A"); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>GitHub Repository:</th>
|
||||
<td><?php echo esc_html($updater ? $updater->getGithubRepo() : "N/A"); ?></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Update Available:</th>
|
||||
<td><?php echo $updater && $updater->isUpdateAvailable() ? "✅ Yes" : "❌ No"; ?></td>
|
||||
</tr>
|
||||
<?php if ($updater && $updater->isUpdateAvailable()) : ?>
|
||||
<tr>
|
||||
<th>Latest Version:</th>
|
||||
<td><?php echo esc_html($updater->getLatestVersion()); ?></td>
|
||||
</tr>
|
||||
<?php endif; ?>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<h2>Manual Update Check</h2>
|
||||
<p>Click the button below to manually check for updates:</p>
|
||||
<button type="button" class="button button-primary" id="mock-plugin-check-update">
|
||||
Check for Updates
|
||||
</button>
|
||||
<div id="mock-plugin-result" style="margin-top: 10px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
jQuery(document).ready(function($) {
|
||||
$("#mock-plugin-check-update").on("click", function() {
|
||||
var button = $(this);
|
||||
var result = $("#mock-plugin-result");
|
||||
|
||||
button.prop("disabled", true).text("Checking...");
|
||||
result.html("");
|
||||
|
||||
$.ajax({
|
||||
url: ajaxurl,
|
||||
type: "POST",
|
||||
data: {
|
||||
action: "mock_plugin_check_version",
|
||||
nonce: "<?php echo wp_create_nonce("mock_plugin_nonce"); ?>"
|
||||
},
|
||||
success: function(response) {
|
||||
if (response.success) {
|
||||
result.html('<div class="notice notice-success"><p>' + response.data.message + '</p></div>');
|
||||
} else {
|
||||
result.html('<div class="notice notice-error"><p>' + response.data + '</p></div>');
|
||||
}
|
||||
},
|
||||
error: function() {
|
||||
result.html('<div class="notice notice-error"><p>Error checking for updates</p></div>');
|
||||
},
|
||||
complete: function() {
|
||||
button.prop("disabled", false).text("Check for Updates");
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
/**
|
||||
* Activation hook
|
||||
*/
|
||||
function mock_plugin_activate(): void
|
||||
{
|
||||
// Clear any existing update caches
|
||||
delete_transient(basename(dirname(__FILE__)) . "_version_check");
|
||||
}
|
||||
register_activation_hook(__FILE__, "mock_plugin_activate");
|
||||
|
||||
/**
|
||||
* Deactivation hook
|
||||
*/
|
||||
function mock_plugin_deactivate(): void
|
||||
{
|
||||
// Clean up transients
|
||||
delete_transient(basename(dirname(__FILE__)) . "_version_check");
|
||||
}
|
||||
register_deactivation_hook(__FILE__, "mock_plugin_deactivate");
|
||||
|
||||
/**
|
||||
* Helper function to get the updater instance (for testing)
|
||||
*
|
||||
* @return Updater|null
|
||||
*/
|
||||
function mock_plugin_get_updater(): ?Updater
|
||||
{
|
||||
return $GLOBALS["mock_plugin_updater"] ?? null;
|
||||
}
|
||||
33
tests/fixtures/mock-plugin/readme.txt
vendored
Normal file
33
tests/fixtures/mock-plugin/readme.txt
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
=== Mock Plugin for WP GitHub Updater Tests ===
|
||||
Contributors: silverassist
|
||||
Tags: testing, github, updater
|
||||
Requires at least: 6.0
|
||||
Tested up to: 6.7
|
||||
Requires PHP: 8.2
|
||||
Stable tag: 1.0.0
|
||||
License: MIT
|
||||
License URI: https://opensource.org/licenses/MIT
|
||||
|
||||
A mock WordPress plugin used for testing the WP GitHub Updater package.
|
||||
|
||||
== Description ==
|
||||
|
||||
This is a test fixture plugin that demonstrates integration with the SilverAssist/WpGithubUpdater package.
|
||||
|
||||
It is used exclusively for automated testing and should not be used in production environments.
|
||||
|
||||
== Installation ==
|
||||
|
||||
This plugin is for testing purposes only and should not be installed on production sites.
|
||||
|
||||
For testing:
|
||||
1. Install WordPress Test Suite using the provided install-wp-tests.sh script
|
||||
2. The plugin will be automatically loaded in the test environment
|
||||
3. Run PHPUnit tests with the WordPress test suite
|
||||
|
||||
== Changelog ==
|
||||
|
||||
= 1.0.0 =
|
||||
* Initial release for testing purposes
|
||||
* Demonstrates WP GitHub Updater integration
|
||||
* Includes admin interface for manual testing
|
||||
14
tests/fixtures/test-plugin.php
vendored
Normal file
14
tests/fixtures/test-plugin.php
vendored
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Plugin Name: Test Plugin for Unit Tests
|
||||
* Description: A minimal test plugin for UpdaterConfig tests
|
||||
* Version: 1.0.0
|
||||
* Author: Test Author
|
||||
* Author URI: https://example.com
|
||||
* Requires at least: 6.0
|
||||
* Requires PHP: 8.2
|
||||
* Text Domain: test-plugin
|
||||
*/
|
||||
|
||||
// This file only exists for testing purposes
|
||||
163
tests/wordpress-mocks.php
Normal file
163
tests/wordpress-mocks.php
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* WordPress function mocks for testing
|
||||
*
|
||||
* This file provides implementations for WordPress functions that are only
|
||||
* defined (but not implemented) in wordpress-stubs.
|
||||
*
|
||||
* @package SilverAssist\WpGithubUpdater\Tests
|
||||
*/
|
||||
|
||||
// Translation functions
|
||||
if (!function_exists("__")) {
|
||||
/**
|
||||
* Mock __ function for tests
|
||||
*
|
||||
* @param string $text Text to translate
|
||||
* @param string $domain Text domain
|
||||
* @return string Translated text (returns original in tests)
|
||||
*/
|
||||
function __(string $text, string $domain = "default"): string
|
||||
{
|
||||
return $text;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists("esc_html__")) {
|
||||
/**
|
||||
* Mock esc_html__ function for tests
|
||||
*
|
||||
* @param string $text Text to translate
|
||||
* @param string $domain Text domain
|
||||
* @return string Escaped and translated text
|
||||
*/
|
||||
function esc_html__(string $text, string $domain = "default"): string
|
||||
{
|
||||
return htmlspecialchars($text, ENT_QUOTES, "UTF-8");
|
||||
}
|
||||
}
|
||||
|
||||
// WordPress hooks and filters
|
||||
if (!function_exists("add_filter")) {
|
||||
/**
|
||||
* Mock add_filter function for tests
|
||||
*
|
||||
* @param string $hook_name Hook name
|
||||
* @param callable $callback Callback function
|
||||
* @param int $priority Priority
|
||||
* @param int $accepted_args Accepted arguments
|
||||
* @return bool Always returns true
|
||||
*/
|
||||
function add_filter(string $hook_name, callable $callback, int $priority = 10, int $accepted_args = 1): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists("add_action")) {
|
||||
/**
|
||||
* Mock add_action function for tests
|
||||
*
|
||||
* @param string $hook_name Hook name
|
||||
* @param callable $callback Callback function
|
||||
* @param int $priority Priority
|
||||
* @param int $accepted_args Accepted arguments
|
||||
* @return bool Always returns true
|
||||
*/
|
||||
function add_action(string $hook_name, callable $callback, int $priority = 10, int $accepted_args = 1): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Plugin functions
|
||||
if (!function_exists("plugin_basename")) {
|
||||
/**
|
||||
* Mock plugin_basename function for tests
|
||||
*
|
||||
* @param string $file Plugin file path
|
||||
* @return string Plugin basename
|
||||
*/
|
||||
function plugin_basename(string $file): string
|
||||
{
|
||||
$file = str_replace("\\", "/", $file);
|
||||
$file = preg_replace("|/+|", "/", $file);
|
||||
|
||||
// If WP_PLUGIN_DIR is defined, make path relative to it
|
||||
if (defined("WP_PLUGIN_DIR")) {
|
||||
$plugin_dir = str_replace("\\", "/", WP_PLUGIN_DIR);
|
||||
$plugin_dir = preg_replace("|/+|", "/", $plugin_dir);
|
||||
$file = preg_replace("#^" . preg_quote($plugin_dir, "#") . "/#", "", $file);
|
||||
}
|
||||
|
||||
// Otherwise just return folder/file.php format
|
||||
$file = trim($file, "/");
|
||||
$parts = explode("/", $file);
|
||||
|
||||
if (count($parts) >= 2) {
|
||||
// Return last two parts: folder/file.php
|
||||
return $parts[count($parts) - 2] . "/" . $parts[count($parts) - 1];
|
||||
}
|
||||
|
||||
return basename($file);
|
||||
}
|
||||
}
|
||||
|
||||
if (!function_exists("get_plugin_data")) {
|
||||
/**
|
||||
* Mock get_plugin_data function for tests
|
||||
*
|
||||
* @param string $plugin_file Path to the plugin file
|
||||
* @param bool $markup Whether to apply markup
|
||||
* @param bool $translate Whether to translate
|
||||
* @return array Plugin data array
|
||||
*/
|
||||
function get_plugin_data(string $plugin_file, bool $markup = true, bool $translate = true): array
|
||||
{
|
||||
if (!file_exists($plugin_file)) {
|
||||
return [
|
||||
"Name" => "Test Plugin",
|
||||
"Version" => "1.0.0",
|
||||
"Description" => "Test plugin description",
|
||||
"Author" => "Test Author",
|
||||
"PluginURI" => "",
|
||||
"AuthorURI" => "",
|
||||
"TextDomain" => "test-plugin",
|
||||
"DomainPath" => "",
|
||||
"Network" => false,
|
||||
"RequiresWP" => "",
|
||||
"RequiresPHP" => "",
|
||||
];
|
||||
}
|
||||
|
||||
$content = file_get_contents($plugin_file);
|
||||
$headers = [
|
||||
"Name" => "Plugin Name",
|
||||
"PluginURI" => "Plugin URI",
|
||||
"Version" => "Version",
|
||||
"Description" => "Description",
|
||||
"Author" => "Author",
|
||||
"AuthorURI" => "Author URI",
|
||||
"TextDomain" => "Text Domain",
|
||||
"DomainPath" => "Domain Path",
|
||||
"Network" => "Network",
|
||||
"RequiresWP" => "Requires at least",
|
||||
"RequiresPHP" => "Requires PHP",
|
||||
];
|
||||
|
||||
$data = [];
|
||||
foreach ($headers as $key => $header) {
|
||||
if (preg_match("/^[ \t\/*#@]*" . preg_quote($header, "/") . ":(.*)$/mi", $content, $matches)) {
|
||||
$data[$key] = trim($matches[1]);
|
||||
} else {
|
||||
$data[$key] = "";
|
||||
}
|
||||
}
|
||||
|
||||
// Convert Network to boolean
|
||||
$data["Network"] = strtolower($data["Network"]) === "true";
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue