diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 2df6464..e39991a 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -1,76 +1,17 @@ # 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.2+**: Modern PHP with strict typing, union types, and nullable parameters +- **PHP 8.0+**: 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 - **Composer Package**: Distributed via Packagist as `silverassist/wp-github-updater` -## πŸ”’ PHPUnit Version Policy - CRITICAL - -**MANDATORY: PHPUnit MUST remain at version 9.6.x** - -### Rationale -- **WordPress Ecosystem Standard**: PHPUnit 9.6 is the most widely used version across WordPress projects -- **WordPress Coding Standards**: The official [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) uses PHPUnit 9.x -- **Yoast PHPUnit Polyfills**: Version 4.x supports PHPUnit 7.5-9.x, 11.x, and 12.x, but **NOT 10.x** -- **Consumer Compatibility**: Most projects depending on this package use PHPUnit 9.6 - -### Version Constraints -```json -{ - "require-dev": { - "phpunit/phpunit": "^9.6", - "yoast/phpunit-polyfills": "^4.0" - } -} -``` - -### What NOT to do -- ❌ Do NOT upgrade PHPUnit to version 10.x (not supported by Yoast PHPUnit Polyfills) -- ❌ Do NOT upgrade to PHPUnit 11.x or 12.x (breaks compatibility with most WordPress projects) -- ❌ Do NOT accept Dependabot PRs that upgrade PHPUnit beyond 9.x - -### Configuration Files -- **dependabot.yml**: Configured to ignore PHPUnit major version updates -- **composer.json**: Locked to `^9.6` version constraint -- **phpunit.xml**: Uses PHPUnit 9.6 schema reference - -### When to Reconsider -Only upgrade PHPUnit when: -1. WordPress Coding Standards officially adopts a newer version -2. Majority of WordPress ecosystem projects have migrated -3. All dependent projects confirm compatibility - ## Project Structure ``` @@ -103,7 +44,6 @@ 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 @@ -112,11 +52,8 @@ 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 and notice dismissal +- AJAX endpoints for manual update checks - 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 @@ -153,34 +90,6 @@ 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 @@ -228,41 +137,13 @@ class Updater { } public function checkForUpdate($transient) { - // WordPress update check integration using isUpdateAvailable() method + // WordPress update check integration } 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 } ``` @@ -396,7 +277,6 @@ $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 ]); @@ -408,9 +288,6 @@ $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) diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index d9a5bbd..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,50 +0,0 @@ -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 - # Ignore PHPUnit major version updates - # Keep at 9.x for WordPress compatibility - ignore: - - dependency-name: "phpunit/phpunit" - update-types: ["version-update:semver-major"] - 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" diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 63a3a81..4b6ca3c 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -13,48 +13,24 @@ on: jobs: build-and-release: - permissions: - contents: write runs-on: ubuntu-latest steps: - name: Checkout code - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 + uses: actions/checkout@v5 - name: Setup PHP - uses: shivammathur/setup-php@bf6b4fbd49ca58e4608c9c89fba0b8d90bd2a39f # v2.35.5 + uses: shivammathur/setup-php@v2 with: - php-version: "8.2" - extensions: mbstring, xml, ctype, json, tokenizer, mysqli + php-version: "8.1" + extensions: mbstring, xml, ctype, json, tokenizer 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: | @@ -100,14 +76,14 @@ jobs: - name: Run tests run: | - echo "πŸ§ͺ Running PHPUnit tests with WordPress Test Suite..." + echo "πŸ§ͺ Running PHPUnit tests..." 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 (including WordPress integration tests)" + echo "βœ… All tests passed" - name: Run code quality checks run: | @@ -162,7 +138,7 @@ jobs: - **Version**: $VERSION - **Namespace**: SilverAssist\\WpGithubUpdater - **License**: PolyForm Noncommercial 1.0.0 - - **PHP Version**: 8.2+ + - **PHP Version**: 8.0+ - **WordPress Version**: 6.0+ ## πŸš€ Installation via Composer @@ -201,7 +177,7 @@ jobs: - **API Docs**: Comprehensive PHPDoc documentation ## πŸ”§ Requirements - - PHP 8.2 or higher + - PHP 8.0 or higher - WordPress 6.0 or higher - Composer for package management - GitHub repository with releases for updates @@ -241,7 +217,7 @@ jobs: \`\`\` ## Requirements - - PHP 8.2+ + - PHP 8.0+ - WordPress 6.0+ - Composer - GitHub repository with releases @@ -282,7 +258,7 @@ jobs: EOF - name: Create GitHub Release - uses: softprops/action-gh-release@5be0e66d93ac7ed76da52eca8bb058f665c3a5fe # v2.4.2 + uses: softprops/action-gh-release@v1 with: tag_name: ${{ steps.version.outputs.tag }} name: "WP GitHub Updater v${{ steps.version.outputs.version }}" diff --git a/CHANGELOG.md b/CHANGELOG.md index b576de1..ea6eb3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,112 +1,5 @@ # 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 diff --git a/README.md b/README.md index 026c727..e84c750 100644 --- a/README.md +++ b/README.md @@ -14,9 +14,6 @@ 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 @@ -63,13 +60,11 @@ $config = new UpdaterConfig( 'plugin_author' => 'Your Name', 'plugin_homepage' => 'https://your-website.com', 'requires_wordpress' => '6.0', - 'requires_php' => '8.3', + 'requires_php' => '8.0', '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', - 'text_domain' => 'my-plugin-textdomain', // For internationalization - 'custom_temp_dir' => WP_CONTENT_DIR . '/temp', // Custom temporary directory (v1.1.3+) + 'ajax_nonce' => 'my_plugin_version_nonce' ] ); @@ -85,13 +80,12 @@ $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.3'` | Minimum PHP version | +| `requires_php` | string | `'8.0'` | 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) @@ -159,7 +153,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.3 + * Requires PHP: 8.0 */ // Prevent direct access @@ -191,174 +185,21 @@ 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.2 or higher +- PHP 8.0 or higher - WordPress 6.0 or higher - Composer for dependency management - Public GitHub repository with releases ## Development -### 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:** +### 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", - ] -); -``` - -### PHPUnit Version Policy - -**This package uses PHPUnit 9.6.x and MUST remain on this version.** - -**Why PHPUnit 9.6?** -- βœ… **WordPress Ecosystem Standard**: Most WordPress projects use PHPUnit 9.6 -- βœ… **WordPress Coding Standards Compatible**: [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) uses PHPUnit 9.x -- βœ… **Yoast PHPUnit Polyfills**: Version 4.x supports PHPUnit 7.5-9.x, 11.x, 12.x, but **NOT 10.x** -- βœ… **Consumer Compatibility**: Projects depending on this package expect PHPUnit 9.6 - -**Do NOT upgrade to:** -- ❌ PHPUnit 10.x (incompatible with Yoast PHPUnit Polyfills 4.x) -- ❌ PHPUnit 11.x or 12.x (breaks compatibility with most WordPress projects) - -**Dependabot Configuration:** -The `.github/dependabot.yml` file is configured to automatically ignore PHPUnit major version updates, ensuring the package remains on 9.x. - ### Code Standards ```bash @@ -378,38 +219,6 @@ 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 diff --git a/composer.json b/composer.json index 95cd4dd..56dd9c8 100644 --- a/composer.json +++ b/composer.json @@ -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.2.1", + "version": "1.1.1", "type": "library", "keywords": [ "wordpress", @@ -20,19 +20,14 @@ } ], "require": { - "php": ">=8.2" + "php": ">=8.0" }, "require-dev": { - "phpunit/phpunit": "^9.6", + "phpunit/phpunit": "^10.0", "squizlabs/php_codesniffer": "^3.7", "phpstan/phpstan": "^1.10", "wp-coding-standards/wpcs": "^3.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": "^4.0" + "slevomat/coding-standard": "^8.0" }, "autoload": { "psr-4": { @@ -46,9 +41,6 @@ }, "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", diff --git a/examples/integration-guide.php b/examples/integration-guide.php index 62c6b49..fb0ea6e 100644 --- a/examples/integration-guide.php +++ b/examples/integration-guide.php @@ -30,19 +30,11 @@ 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 - "custom_temp_dir" => WP_CONTENT_DIR . "/temp", // Enhanced error handling (v1.1.3+) + "text_domain" => "silver-assist-security" // Your plugin's text domain ] ); - $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); - } + new Updater($config); }); */ @@ -67,19 +59,11 @@ 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 - "custom_temp_dir" => wp_upload_dir()["basedir"] . "/temp", // Alternative temp dir location + "text_domain" => "leadgen-app-form" // Your plugin's text domain ] ); - $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(); - } - }); + new Updater($config); }); */ @@ -118,76 +102,14 @@ 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 - "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 '
'; - echo '

My Plugin: Update available from ' . esc_html($currentVersion) . ' to ' . esc_html($latestVersion) . '

'; - echo '
'; - } - }); -}); - -// 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", + "text_domain" => "my-new-plugin" // Your plugin's text domain ] ); 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); - } - }); }); -*/ -/** - * 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 +// Your plugin code here... */ /** @@ -199,22 +121,3 @@ define('WP_TEMP_DIR', ABSPATH . 'wp-content/temp'); * 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 - */ diff --git a/phpcs.xml b/phpcs.xml index 830a943..8aa13d4 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -47,12 +47,6 @@ - - - - tests/bootstrap.php - tests/fixtures/* - diff --git a/phpstan.neon b/phpstan.neon index dffa685..65ed03a 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,16 +1,9 @@ -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: - # Ignore WP_Error union type issues in some contexts - - '#Cannot call method get_error_message\(\) on string\|WP_Error\|false#' + # WordPress functions are not available during static analysis + - '#Call to unknown function: [\\]?[a-zA-Z_]+#' diff --git a/phpunit.xml b/phpunit.xml index fb63689..8498252 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,37 +1,15 @@ - + colors="true"> - - tests/Unit - - - tests/Integration - - - tests/WordPress + + tests - + ./src - - - - - - - - - - - - - - + diff --git a/scripts/install-wp-tests.sh b/scripts/install-wp-tests.sh deleted file mode 100755 index 63a2b7c..0000000 --- a/scripts/install-wp-tests.sh +++ /dev/null @@ -1,255 +0,0 @@ -#!/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-host] [wp-version] - -if [ $# -lt 3 ]; then - echo "" - echo "❌ ERROR: Missing required arguments" - echo "" - echo "Usage: $0 [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 " " -echo "" -echo "Now you can run tests with:" -echo " vendor/bin/phpunit --testdox" -echo "" diff --git a/scripts/test-runner.sh b/scripts/test-runner.sh deleted file mode 100755 index a9b319d..0000000 --- a/scripts/test-runner.sh +++ /dev/null @@ -1,231 +0,0 @@ -#!/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-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 diff --git a/src/Updater.php b/src/Updater.php index 66425c5..1ff25fa 100644 --- a/src/Updater.php +++ b/src/Updater.php @@ -7,15 +7,12 @@ * * @package SilverAssist\WpGithubUpdater * @author Silver Assist - * @version 1.2.1 + * @version 1.1.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 * @@ -118,12 +115,6 @@ 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"]); } /** @@ -145,7 +136,7 @@ class Updater $latestVersion = $this->getLatestVersion(); - if ($this->isUpdateAvailable()) { + if ($latestVersion && version_compare($this->currentVersion, $latestVersion, "<")) { $transient->response[$this->pluginSlug] = (object) [ "slug" => $this->pluginBasename, "plugin" => $this->pluginSlug, @@ -212,7 +203,7 @@ class Updater * * @since 1.0.0 */ - public function getLatestVersion(): string|false + private function getLatestVersion(): string|false { // Check cache first $cachedVersion = \get_transient($this->versionTransient); @@ -393,10 +384,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"])) { @@ -427,31 +418,14 @@ 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" => $updateAvailable, + "update_available" => $latestVersion && version_compare($this->currentVersion, $latestVersion, "<"), "github_repo" => $this->config->githubRepo, - "notice_set" => $updateAvailable, // Indicate if notice was set ]); } catch (\Exception $e) { \wp_send_json_error([ @@ -461,99 +435,6 @@ 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 '
'; - echo '

'; - echo '' . \esc_html($plugin_name) . ' '; - echo sprintf( - \esc_html($this->config->__("has a new version available: %1\$s (you have %2\$s).")), - '' . \esc_html($latest_version) . '', - \esc_html($current_version) - ); - echo '

'; - echo '

'; - echo '' . - \esc_html($this->config->__("View Updates")) . ' '; - echo '' . - \esc_html($this->config->__("View Release Notes")) . ''; - echo '

'; - echo '
'; - - // Add JavaScript to handle dismissal - echo ''; - } - /** * Get plugin data from file */ @@ -650,61 +531,24 @@ class Updater /** * Maybe fix download issues by providing better HTTP args * - * 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 + * @return boolean|\WP_Error $result * * @since 1.1.0 */ public function maybeFixDownload( - bool|WP_Error $result, + bool|\WP_Error $result, string $package, object $upgrader, array $hook_extra - ): string|WP_Error|false { - // If a previous filter already handled this, respect that decision - if (\is_wp_error($result)) { + ): bool|\WP_Error { + // Only handle GitHub downloads for our plugin + if (!str_contains($package, "github.com") || !str_contains($package, $this->config->githubRepo)) { return $result; } - // 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 + // Use wp_remote_get with better parameters $args = [ - "timeout" => 300, // 5 minutes for large files + "timeout" => 300, // 5 minutes "headers" => $this->getDownloadHeaders(), "sslverify" => true, "stream" => false, @@ -714,183 +558,32 @@ class Updater $response = \wp_remote_get($package, $args); if (\is_wp_error($response)) { - return new WP_Error( - "download_failed", - sprintf( - $this->config->__("Failed to download package: %s"), - $response->get_error_message() - ) - ); + return $response; } - $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 - ) - ); + if (200 !== \wp_remote_retrieve_response_code($response)) { + return new \WP_Error("http_404", $this->config->__("Package not found")); } - // 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") - ); + // 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")); } - // 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"); + $file_handle = @fopen($temp_file, "w"); if (!$file_handle) { - @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 - ) - ); + return new \WP_Error("file_open_failed", $this->config->__("Could not open file for writing")); } - $bytes_written = fwrite($file_handle, $body); + fwrite($file_handle, \wp_remote_retrieve_body($response)); 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 * diff --git a/src/UpdaterConfig.php b/src/UpdaterConfig.php index bb85086..11d617a 100644 --- a/src/UpdaterConfig.php +++ b/src/UpdaterConfig.php @@ -7,7 +7,7 @@ * * @package SilverAssist\WpGithubUpdater * @author Silver Assist - * @version 1.2.1 + * @version 1.1.1 * @license PolyForm-Noncommercial-1.0.0 */ @@ -130,14 +130,6 @@ 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 * @@ -164,13 +156,12 @@ 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.2"; + $this->requiresPHP = $options["requires_php"] ?? "8.0"; $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; } /** @@ -186,7 +177,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); } diff --git a/tests/Integration/DownloadFilterTest.php b/tests/Integration/DownloadFilterTest.php deleted file mode 100644 index 78c3362..0000000 --- a/tests/Integration/DownloadFilterTest.php +++ /dev/null @@ -1,147 +0,0 @@ -testPluginFile = sys_get_temp_dir() . "/test-plugin.php"; - file_put_contents($this->testPluginFile, "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"); - } -} diff --git a/tests/Integration/RealGitHubAPITest.php b/tests/Integration/RealGitHubAPITest.php deleted file mode 100644 index 9b5ac5a..0000000 --- a/tests/Integration/RealGitHubAPITest.php +++ /dev/null @@ -1,423 +0,0 @@ -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" - ); - } -} diff --git a/tests/Integration/UpdaterIntegrationTest.php b/tests/Integration/UpdaterIntegrationTest.php deleted file mode 100644 index 4858f8c..0000000 --- a/tests/Integration/UpdaterIntegrationTest.php +++ /dev/null @@ -1,142 +0,0 @@ -testPluginFile = sys_get_temp_dir() . "/test-plugin.php"; - file_put_contents($this->testPluginFile, "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); - } -} diff --git a/tests/Unit/UpdaterConfigTest.php b/tests/UpdaterConfigTest.php similarity index 79% rename from tests/Unit/UpdaterConfigTest.php rename to tests/UpdaterConfigTest.php index 33da8a8..a20451d 100644 --- a/tests/Unit/UpdaterConfigTest.php +++ b/tests/UpdaterConfigTest.php @@ -1,27 +1,20 @@ assertEquals(self::$testPluginFile, $config->pluginFile); + $this->assertEquals("/path/to/plugin.php", $config->pluginFile); $this->assertEquals("owner/repo", $config->githubRepo); $this->assertEquals("6.0", $config->requiresWordPress); - $this->assertEquals("8.2", $config->requiresPHP); + $this->assertEquals("8.0", $config->requiresPHP); $this->assertEquals("{slug}-v{version}.zip", $config->assetPattern); $this->assertEquals("wp-github-updater", $config->textDomain); } @@ -41,7 +34,7 @@ class UpdaterConfigTest extends TestCase "text_domain" => "my-custom-plugin" ]; - $config = new UpdaterConfig(self::$testPluginFile, "owner/repo", $options); + $config = new UpdaterConfig("/path/to/plugin.php", "owner/repo", $options); $this->assertEquals("Test Plugin", $config->pluginName); $this->assertEquals("A test plugin", $config->pluginDescription); @@ -57,7 +50,7 @@ class UpdaterConfigTest extends TestCase public function testTranslationMethods(): void { - $config = new UpdaterConfig(self::$testPluginFile, "owner/repo", [ + $config = new UpdaterConfig("/path/to/plugin.php", "owner/repo", [ "text_domain" => "test-domain" ]); diff --git a/tests/WordPress/MockPluginTest.php b/tests/WordPress/MockPluginTest.php deleted file mode 100644 index 90f5dc0..0000000 --- a/tests/WordPress/MockPluginTest.php +++ /dev/null @@ -1,258 +0,0 @@ -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(); - } -} diff --git a/tests/WordPress/WordPressHooksTest.php b/tests/WordPress/WordPressHooksTest.php deleted file mode 100644 index 51c0658..0000000 --- a/tests/WordPress/WordPressHooksTest.php +++ /dev/null @@ -1,213 +0,0 @@ -testPluginFile = sys_get_temp_dir() . "/test-plugin.php"; - file_put_contents( - $this->testPluginFile, - "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); - } -} diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 074bf99..e6334bb 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -1,117 +1,28 @@ "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 diff --git a/tests/fixtures/mock-plugin/mock-plugin.php b/tests/fixtures/mock-plugin/mock-plugin.php deleted file mode 100644 index eacca2b..0000000 --- a/tests/fixtures/mock-plugin/mock-plugin.php +++ /dev/null @@ -1,213 +0,0 @@ - "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; - - ?> -
-

- -
-

Plugin Information

- - - - - - - - - - - - - - isUpdateAvailable()) : ?> - - - - - -
Current Version:getCurrentVersion() : "N/A"); ?>
GitHub Repository:getGithubRepo() : "N/A"); ?>
Update Available:isUpdateAvailable() ? "βœ… Yes" : "❌ No"; ?>
Latest Version:getLatestVersion()); ?>
-
- -
-

Manual Update Check

-

Click the button below to manually check for updates:

- -
-
-
- - - = 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; - } -}