From d523565c793e6b08a43859a7e0adb23ffe6f159b Mon Sep 17 00:00:00 2001 From: Javier Casares Date: Wed, 4 Feb 2026 09:26:54 +0000 Subject: [PATCH] v1.1.1 --- changelog.txt | 211 +++++++++++++++++++++ idrivee2-media-upload.php | 2 +- readme.txt | 301 ++++++++++++++++++++++++++---- robotstxt-updater.php | 383 ++++++++++++++++++++++++++++++++++++++ update.json | 27 +++ 5 files changed, 890 insertions(+), 34 deletions(-) create mode 100644 changelog.txt create mode 100644 robotstxt-updater.php create mode 100644 update.json diff --git a/changelog.txt b/changelog.txt new file mode 100644 index 0000000..cd908a1 --- /dev/null +++ b/changelog.txt @@ -0,0 +1,211 @@ +== Changelog == + += 1.1.1 = + +_Release date: 2026-02-04_ + +**Highlights** + +* Deployment script improvements to ensure production packages are complete + +**Fixed** + +* Deployment script now includes essential files in production packages +* update.json (auto-update system) now included in deploy +* robotstxt-updater.php (auto-update handler) now included in deploy +* readme.txt (WordPress.org documentation) now included in deploy +* changelog.txt (full changelog) now included in deploy +* Updated script documentation and output messages + +**Improved** + +* Production packages now contain all files required for automatic updates from Gitea +* Deploy script version updated to 1.1.0 + +**Compatibility** + +* WordPress: 6.8 - 6.9 +* PHP: 8.2 - 8.4 +* MariaDB: 10.6+ + +**Tests** + +* PHP Coding Standards: 0 errors +* WordPress Coding Standards (WPCS): 3.3 +* PHPStan: Level 8 (0 errors) +* PHPCompatibility: 8.2-8.4 + += 1.1.0 = + +_Release date: 2026-02-04_ + +**Highlights** + +* Configuration and consistency improvements for PHP 8.2+ compatibility +* Updated plugin repository URLs to Gitea +* Fixed text domain consistency across all files + +**Changed** + +* Added explicit PHP version requirement (>=8.2) to composer.json +* Updated update.json with correct plugin information +* Fixed Text Domain in robotstxt-updater.php to match plugin slug (idrivee2-media-upload) +* Migrated repository from GitHub to Gitea (git.robotstxt.es) +* Added Gitea Plugin URI and Primary Branch headers + +**Fixed** + +* Composer now validates PHP version during dependency installation +* Plugin update system correctly identifies the plugin +* Translations properly loaded for updater error messages + +**Improved** + +* All text domains now consistently use 'idrivee2-media-upload' +* Update metadata accurately reflects plugin information + +**Compatibility** + +* WordPress: 6.8 - 6.9 +* PHP: 8.2 - 8.4 +* MariaDB: 10.6+ + +**Tests** + +* PHP Coding Standards: 0 errors +* WordPress Coding Standards (WPCS): 3.3 +* PHPStan: Level 8 (0 errors) +* PHPCompatibility: 8.2-8.4 + += 1.0.0 = + +_Release date: 2026-02-03_ + +**Highlights** + +* First stable production-ready release +* Enterprise-grade security with comprehensive logging +* OWASP Top 10 (2021) compliance +* PHPStan level 8 compliance with zero errors +* Security rating: A+ (Excellent) + +**Added** + +* Security logging system with comprehensive audit trail +* Rate limiting protection (60s users, 30s admins) +* S3 operation statistics tracking (30-day retention) +* Logger class for security and operations logging +* Rate_Limiter class for abuse prevention +* Comprehensive security audit documentation (7,500+ lines) +* Code quality report with metrics (3,200+ lines) + +**Changed** + +* Capability checks switched from role checks to manage_options +* Type safety with strict type hints throughout +* Error messages now user-friendly with translations +* Admin interface shows rate limit feedback + +**Fixed** + +* All 17 PHPStan level 8 type safety issues resolved +* Array type specifications added to all methods +* Null handling for AWS error messages +* Return type declarations match actual returns +* All capability checks use manage_options + +**Security** + +* OWASP Top 10 (2021) 100% compliance +* Enhanced nonce validation +* Comprehensive input sanitization and output escaping +* Security logging for all critical operations +* Rate limiting to prevent brute force attacks + +**Compatibility** + +* WordPress: 6.8 - 6.9 +* PHP: 8.2 - 8.4 +* MariaDB: 10.6+ + +**Tests** + +* PHP Coding Standards: 0 errors +* WordPress Coding Standards (WPCS): 3.3 +* PHPStan: Level 8 (0 errors) +* PHPCompatibility: 8.2-8.4 + += 0.3.0 = + +_Release date: 2025-02-03_ + +**Highlights** + +* Complete refactoring to class-based architecture +* Modular file structure with dependency injection +* Added PHPUnit test framework +* Moved from Media to Settings menu + +**Added** + +* Settings page in WordPress Admin (Settings → iDrivee2) +* Timestamped test files (test-YYYYMMDDHHMMSS.txt) +* Persistent test files with delete capability +* Database-stored configuration option +* Hybrid configuration (wp-config.php + database) +* Class-based architecture with 6 classes +* PHPUnit test structure +* PHPStan static analysis +* Deployment script (bin/deploy.sh) +* Uninstall script +* Composer scripts (test, phpcs, phpstan, lint) + +**Changed** + +* Menu location from Media → iDrivee2 to Settings → iDrivee2 +* Architecture from functional to object-oriented +* JavaScript version from hardcoded to dynamic + +**Fixed** + +* Code duplication eliminated (7 instances) +* WordPress Coding Standards violations +* PHP compatibility issues +* Type safety with strict declarations + +**Compatibility** + +* WordPress: 6.7 - 6.9 +* PHP: 8.2 - 8.4 +* MariaDB: 10.6+ + +**Tests** + +* PHP Coding Standards: 0 errors +* WordPress Coding Standards (WPCS): 3.3 +* PHPStan: Basic structure added +* PHPCompatibility: 8.2+ + += 0.1.13 = + +_Release date: 2024-XX-XX_ + +**Highlights** + +* Initial public release +* Basic S3-compatible storage integration + +**Added** + +* Automatic media upload to S3 +* Local file deletion after upload +* CDN URL rewriting +* WordPress admin testing interface +* Configuration via wp-config.php constants +* Multisite support + +**Compatibility** + +* WordPress: 6.5+ +* PHP: 7.4+ +* MariaDB: 10.6+ diff --git a/idrivee2-media-upload.php b/idrivee2-media-upload.php index 4d5cdd1..a1de888 100644 --- a/idrivee2-media-upload.php +++ b/idrivee2-media-upload.php @@ -5,7 +5,7 @@ * Gitea Plugin URI: https://git.robotstxt.es/ROBOTSTXT/idrivee2-media-upload * Primary Branch: main * Description: Uploads media files to iDrivee2 (S3-compatible) with enterprise-grade security and logging. - * Version: 1.1.0 + * Version: 1.1.1 * Requires at least: 6.8 * Requires PHP: 8.2 * Author: Javier Casares diff --git a/readme.txt b/readme.txt index 6d9c51e..6be4e05 100644 --- a/readme.txt +++ b/readme.txt @@ -1,66 +1,301 @@ === iDrivee2 Media Upload === Contributors: javiercasares -Tags: s3, cdn, media, upload, storage +Tags: media, upload, s3, cdn, storage, idrivee2, cloud Requires at least: 6.8 Tested up to: 6.9 +Stable tag: 1.1.1 Requires PHP: 8.2 -Stable tag: 1.0.0 +Version: 1.1.1 License: GPL-2.0-or-later License URI: https://spdx.org/licenses/GPL-2.0-or-later.html -Uploads media files to iDrivee2 (S3-compatible storage) with enterprise-grade security and logging. +Upload media files to iDrivee2 (S3-compatible storage) with enterprise-grade security and logging. == Description == -WordPress plugin that uploads media files to iDrivee2 (S3-compatible storage), deletes local copies, and serves media from a CDN. Enterprise-grade security with comprehensive logging and rate limiting. +iDrivee2 Media Upload is a WordPress plugin that automatically uploads media files to iDrivee2 (S3-compatible storage), deletes local copies to save disk space, and rewrites URLs to serve media from a CDN. The plugin features enterprise-grade security with comprehensive logging, rate limiting, and OWASP Top 10 compliance. -**Features:** +**Key Features:** + +* **Automatic S3 Upload**: All media files and generated sizes are automatically uploaded to S3-compatible storage +* **Local File Cleanup**: Deletes local files after successful upload to save disk space +* **CDN Integration**: Rewrites WordPress media URLs to serve from custom CDN domain +* **Security Logging**: Comprehensive logging system for all security events and S3 operations +* **Rate Limiting**: Protection against abuse with configurable cooldown periods +* **Admin Interface**: Test S3 connection and upload test files from WordPress admin +* **Multisite Support**: Works seamlessly with WordPress Multisite installations +* **Type-Safe Code**: PHPStan level 8 compliance with strict type declarations +* **OWASP Compliant**: All OWASP Top 10 (2021) vulnerabilities addressed + +**Security Features:** -* Automatic upload to S3-compatible storage -* Local file cleanup to save disk space -* CDN integration for faster media delivery * Security logging with WP_DEBUG_LOG integration -* Rate limiting to prevent abuse -* OWASP Top 10 (2021) compliant -* PHPStan level 8 type-safe code -* WordPress.org coding standards compliant +* Rate limiting (60s users, 30s admins) +* S3 operation statistics tracking (30-day retention) +* Comprehensive nonce validation +* Input sanitization and output escaping +* Capability-based access control (manage_options) +* Sensitive data masking in logs +* Security rating: A+ (Excellent) -**Security Rating: A+ (Excellent)** +**Requirements:** + +* WordPress 6.8 or higher +* PHP 8.2, 8.3, or 8.4 +* MariaDB 10.6+ or MySQL 5.7+ +* S3-compatible storage (iDrivee2, AWS S3, DigitalOcean Spaces, etc.) + +== Extra Configurations == + +The plugin requires configuration constants in your `wp-config.php` file. Add these constants before the `/* That's all, stop editing! */` line: + +**Required Constants:** + +`define('IDRIVEE2_MEDIA_HOST', 'https://your-s3-endpoint.com');` +S3 endpoint URL. Must start with `https://`. + +`define('IDRIVEE2_MEDIA_KEY', 'YOUR_ACCESS_KEY_ID');` +S3 Access Key ID for authentication. + +`define('IDRIVEE2_MEDIA_SECRET', 'YOUR_SECRET_ACCESS_KEY');` +S3 Secret Access Key for authentication. + +`define('IDRIVEE2_MEDIA_BUCKET', 'your-bucket-name');` +S3 bucket name where media files will be stored. + +`define('IDRIVEE2_MEDIA_REGION', 'us-east-1');` +AWS region for the S3 service (e.g., 'us-east-1', 'eu-west-1'). + +**Optional Constants:** + +`define('IDRIVEE2_MEDIA_DOMAIN', 'https://cdn.yourdomain.com');` +Custom CDN domain for serving media files. If not defined, files will be served directly from S3 ObjectURL. + +**Security Logging:** + +To enable security logging, add these constants: + +`define('WP_DEBUG', false);` +Disable debug mode in production. + +`define('WP_DEBUG_LOG', true);` +Enable logging to `wp-content/debug.log`. + +`define('WP_DEBUG_DISPLAY', false);` +Don't display errors on screen. + +**Example Configuration:** + +`// iDrivee2 Media Upload Configuration` +`define('IDRIVEE2_MEDIA_HOST', 'https://s3.idrivee2.com');` +`define('IDRIVEE2_MEDIA_KEY', 'YOUR_ACCESS_KEY_ID');` +`define('IDRIVEE2_MEDIA_SECRET', 'YOUR_SECRET_ACCESS_KEY');` +`define('IDRIVEE2_MEDIA_BUCKET', 'my-wordpress-media');` +`define('IDRIVEE2_MEDIA_REGION', 'us-east-1');` +`define('IDRIVEE2_MEDIA_DOMAIN', 'https://cdn.example.com');` + +`// Enable Security Logging` +`define('WP_DEBUG', false);` +`define('WP_DEBUG_LOG', true);` +`define('WP_DEBUG_DISPLAY', false);` == Installation == -1. Upload the plugin files to `/wp-content/plugins/idrivee2-media-upload/` -2. Activate the plugin through the 'Plugins' screen in WordPress -3. Configure S3 credentials in Settings → iDrivee2 -4. Test the connection and start uploading += Automatic download = + +1. Visit Plugins → Add New in your WordPress admin +2. Search for "iDrivee2 Media Upload" +3. Click "Install Now" and then "Activate" +4. Configure the required constants in `wp-config.php` (see Extra Configurations) +5. Go to Settings → iDrivee2 to test your configuration + += Manual download = + +1. Download the plugin ZIP file +2. Extract the contents and upload to `/wp-content/plugins/idrivee2-media-upload/` +3. Activate the plugin through the Plugins menu in WordPress +4. Configure the required constants in `wp-config.php` (see Extra Configurations) +5. Go to Settings → iDrivee2 to test your configuration + += After Installation = + +1. Add the required constants to your `wp-config.php` file +2. Navigate to Settings → iDrivee2 in WordPress admin +3. Click "Test S3 Connection" to verify bucket access +4. Click "Upload Test File" to verify upload capability +5. Upload a test image through Media → Add New +6. Verify the image URL points to your S3/CDN domain +7. Verify local file is deleted after upload == Frequently Asked Questions == -= Does this plugin delete my local files? = += Does this plugin delete local media files? = -Yes. After successfully uploading to S3, local files are deleted to save disk space. Make sure your S3 configuration is correct before using. +Yes. This plugin deletes local media files after successful upload to S3. Ensure your S3 configuration is correct before activation. Files are permanently deleted from your server to save disk space. -= Can I use my own CDN domain? = += What happens if S3 upload fails? = -Yes. Set the IDRIVEE2_MEDIA_DOMAIN constant in wp-config.php or configure it in Settings → iDrivee2. +If the S3 upload fails, the local file is NOT deleted. The error is logged (if WP_DEBUG_LOG is enabled) and the file remains on your server. -= Is this compatible with Multisite? = += Are uploaded files publicly accessible? = -Yes. The plugin fully supports WordPress Multisite installations. +Yes. Files are uploaded with `public-read` ACL. All uploaded files are publicly accessible via the S3 URL or your CDN domain. + += Can I use this with any S3-compatible service? = + +Yes. The plugin works with any S3-compatible service including iDrivee2, AWS S3, DigitalOcean Spaces, Wasabi, Backblaze B2, and others. + += Does this work with WordPress Multisite? = + +Yes. The plugin is fully compatible with WordPress Multisite installations (Network: true). + += What is logged by the security logging system? = + +The plugin logs configuration changes (with sensitive data masking), S3 operations (success/failure with error details), rate limit violations, authentication failures, and invalid file upload attempts. All logs include user context (username, user ID) and timestamps. + += How do I view the security logs? = + +Enable `WP_DEBUG_LOG` in `wp-config.php`. Logs are written to `wp-content/debug.log`. You can view this file via FTP/SFTP or use a log viewer plugin. + += What is the rate limiting feature? = + +Rate limiting prevents abuse of the test functions. Regular users have a 60-second cooldown between actions, administrators have a 30-second cooldown. This prevents brute force testing of S3 credentials. + += Can I disable rate limiting? = + +Rate limiting is built-in and cannot be disabled. However, administrators have a shorter cooldown (30s vs 60s). + += Does this plugin modify the WordPress database? = + +Yes. The plugin updates attachment GUIDs to S3 URLs. It also stores S3 operation statistics in the database with 30-day retention. The uninstall script removes all plugin data when the plugin is deleted. + += Is this plugin compatible with page caching plugins? = + +Yes. The URL rewriting happens at the WordPress level, so it works with all caching plugins. + += What PHP version is required? = + +PHP 8.2 or higher is required. The plugin uses strict type declarations and is tested on PHP 8.2, 8.3, and 8.4. + +== Compatibility == + +* WordPress: 6.8 - 6.9 +* PHP: 8.2 - 8.4 +* MariaDB: 10.6+ +* MySQL: 5.7+ + +**Code Quality:** + +* PHP Coding Standards: 0 errors +* WordPress Coding Standards (WPCS): 3.3 (0 violations) +* PHPStan: Level 8 (0 errors, maximum strictness) +* PHPCompatibility: 8.2-8.4 (fully compatible) == Changelog == -= 1.0.0 = -* First stable release with enterprise-grade security -* Added security logging system -* Added rate limiting protection -* PHPStan level 8 compliance -* OWASP Top 10 compliant -* Complete security audit (A+ rating) += 1.1.1 = -See CHANGELOG.md on GitHub for detailed version history. +_Release date: 2026-02-04_ -== Upgrade Notice == +**Fixed** + +* Deployment script now includes essential files (update.json, robotstxt-updater.php, readme.txt, changelog.txt) +* Production packages now contain all files required for automatic updates from Gitea + += 1.1.0 = + +_Release date: 2026-02-04_ + +**Changed** + +* Added explicit PHP version requirement (>=8.2) to composer.json +* Updated update.json with correct plugin information +* Fixed Text Domain in robotstxt-updater.php to match plugin slug (idrivee2-media-upload) +* Migrated repository from GitHub to Gitea (git.robotstxt.es) +* Added Gitea Plugin URI and Primary Branch headers + +**Fixed** + +* Composer now validates PHP version during dependency installation +* Plugin update system correctly identifies the plugin +* Translations properly loaded for updater error messages + +**Improved** + +* All text domains now consistently use 'idrivee2-media-upload' +* Update metadata accurately reflects plugin information = 1.0.0 = -First production-ready release with comprehensive security features. + +_Release date: 2026-02-03_ + +**Added** + +* Security logging system with comprehensive audit trail +* Rate limiting protection (60s users, 30s admins) +* S3 operation statistics tracking (30-day retention) +* Logger class for security and operations logging +* Rate_Limiter class for abuse prevention +* Comprehensive security audit documentation (7,500+ lines) +* Code quality report with metrics (3,200+ lines) + +**Security** + +* OWASP Top 10 (2021) 100% compliance +* Enhanced nonce validation +* Comprehensive input sanitization and output escaping +* Security logging for all critical operations +* Security rating: A+ (Excellent) + +**Fixed** + +* All 17 PHPStan level 8 type safety issues resolved +* Array type specifications added to all methods +* Null handling for AWS error messages +* Return type declarations match actual returns + += 0.3.0 = + +_Release date: 2025-02-03_ + +**Added** + +* Settings page in WordPress Admin (Settings → iDrivee2) +* Class-based architecture with 6 classes +* PHPUnit test structure +* PHPStan static analysis +* Deployment script (bin/deploy.sh) + +**Changed** + +* Menu location from Media → iDrivee2 to Settings → iDrivee2 +* Architecture from functional to object-oriented + +**Fixed** + +* Code duplication eliminated (7 instances) +* WordPress Coding Standards violations + += Previous versions = + +If you want to see the full changelog, visit the [changelog.txt](https://git.robotstxt.es/ROBOTSTXT/idrivee2-media-upload/raw/branch/main/changelog.txt) file. + +== Compliance == + +This plugin adheres to the following security measures and review protocols for each version: + +* [WordPress Plugin Handbook](https://developer.wordpress.org/plugins/) +* [WordPress Plugin Security](https://developer.wordpress.org/plugins/wordpress-org/plugin-security/) +* [WordPress APIs Security](https://developer.wordpress.org/apis/security/) +* [WordPress Coding Standards](https://github.com/WordPress/WordPress-Coding-Standards) +* [Plugin Check (PCP)](https://wordpress.org/plugins/plugin-check/) +* [OWASP Top 10 (2021)](https://owasp.org/Top10/) +* [PHPStan Level 8](https://phpstan.org/user-guide/rule-levels) + +**Security Audit:** + +A comprehensive security audit is available at `docs/SECURITY-AUDIT.md` covering all OWASP Top 10 vulnerabilities, WordPress.org Plugin Review requirements, and security best practices. + +**Code Quality:** + +A detailed code quality report is available at `docs/QUALITY-REPORT.md` with metrics, static analysis results, and maintainability scores. diff --git a/robotstxt-updater.php b/robotstxt-updater.php new file mode 100644 index 0000000..80d556b --- /dev/null +++ b/robotstxt-updater.php @@ -0,0 +1,383 @@ +register(); + } + + /** + * Constructor. + * + * @param string $plugin_file_path Absolute path to the main plugin file. + */ + private function __construct( string $plugin_file_path ) { + $this->plugin_file_path = $plugin_file_path; + $this->plugin_basename = plugin_basename( $plugin_file_path ); + $this->plugin_slug = dirname( $this->plugin_basename ); + $this->plugin_data = $this->get_plugin_data(); + $this->json_url = $this->build_json_url(); + $this->cache_key = 'robotstxt_updater_' . md5( $this->plugin_basename ); + } + + /** + * Register WordPress hooks. + */ + private function register(): void { + add_filter( 'pre_set_site_transient_update_plugins', array( $this, 'inject_update_info' ) ); + add_filter( 'plugins_api', array( $this, 'provide_plugin_details' ), 10, 3 ); + add_action( 'admin_init', array( $this, 'handle_cache_clear' ) ); + add_action( 'robotstxt_updater_clear_cache', array( $this, 'clear_cache' ) ); + } + + /** + * Get plugin headers. + * + * @return array Plugin data. + */ + private function get_plugin_data(): array { + if ( ! function_exists( 'get_plugin_data' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + return get_plugin_data( $this->plugin_file_path, false, false ); + } + + /** + * Build JSON URL from plugin headers. + * + * Tries to use "Gitea Plugin URI" header to construct the URL. + * Falls back to Plugin URI if Gitea URI is not available. + * + * @return string JSON URL. + */ + private function build_json_url(): string { + // Try Gitea Plugin URI (format: "OWNER/REPO" or full URL). + if ( ! empty( $this->plugin_data['Gitea Plugin URI'] ) ) { + $gitea_uri = $this->plugin_data['Gitea Plugin URI']; + + // If it's already a full URL, use it. + if ( str_starts_with( $gitea_uri, 'http' ) ) { + // Extract base URL and construct JSON path. + return rtrim( $gitea_uri, '/' ) . '/raw/branch/main/update.json'; + } + + // If it's in format "OWNER/REPO", construct full URL. + if ( preg_match( '#^[^/]+/[^/]+$#', $gitea_uri ) ) { + return "https://git.robotstxt.es/{$gitea_uri}/raw/branch/main/update.json"; + } + } + + // Fallback: try to extract from Plugin URI. + if ( ! empty( $this->plugin_data['PluginURI'] ) ) { + $plugin_uri = $this->plugin_data['PluginURI']; + if ( str_contains( $plugin_uri, 'git.robotstxt.es' ) ) { + return rtrim( $plugin_uri, '/' ) . '/raw/branch/main/update.json'; + } + } + + // Last resort: construct from plugin slug. + return "https://git.robotstxt.es/ROBOTSTXT/{$this->plugin_slug}/raw/branch/main/update.json"; + } + + /** + * Inject update info into WP's plugin update transient. + * + * @param object|mixed $transient The update_plugins transient. + * + * @return object The modified transient. + */ + public function inject_update_info( $transient ) { + if ( ! is_object( $transient ) ) { + $transient = new stdClass(); + } + + if ( empty( $transient->checked ) || ! is_array( $transient->checked ) ) { + return $transient; + } + + if ( empty( $transient->checked[ $this->plugin_basename ] ) ) { + return $transient; + } + + $current_version = $transient->checked[ $this->plugin_basename ]; + $remote = $this->get_remote_data(); + + if ( empty( $remote['version'] ) || empty( $remote['download_url'] ) ) { + return $transient; + } + + if ( ! $this->is_compatible( $remote ) ) { + return $transient; + } + + if ( version_compare( $remote['version'], $current_version, '>' ) ) { + $update = (object) array( + 'slug' => $remote['slug'] ?? $this->plugin_slug, + 'plugin' => $this->plugin_basename, + 'new_version' => $remote['version'], + 'url' => $remote['homepage'] ?? $this->plugin_data['PluginURI'] ?? '', + 'package' => $remote['download_url'], + 'tested' => $remote['tested'] ?? '', + 'requires' => $remote['requires'] ?? '', + 'requires_php' => $remote['requires_php'] ?? '', + ); + + $transient->response[ $this->plugin_basename ] = $update; + } + + return $transient; + } + + /** + * Provide "View details" modal content. + * + * @param false|object|array $result The result object or array. + * @param string $action The type of information being requested. + * @param object $args Plugin API arguments. + * + * @return false|object The plugin information object or false. + */ + public function provide_plugin_details( $result, string $action, object $args ) { + if ( 'plugin_information' !== $action ) { + return $result; + } + + if ( empty( $args->slug ) || $args->slug !== $this->plugin_slug ) { + return $result; + } + + $remote = $this->get_remote_data(); + + if ( empty( $remote['version'] ) ) { + return $result; + } + + return (object) array( + 'name' => $remote['name'] ?? $this->plugin_data['Name'] ?? $this->plugin_slug, + 'slug' => $remote['slug'] ?? $this->plugin_slug, + 'version' => $remote['version'], + 'author' => $remote['author'] ?? $this->plugin_data['Author'] ?? '', + 'homepage' => $remote['homepage'] ?? $this->plugin_data['PluginURI'] ?? '', + 'requires' => $remote['requires'] ?? '', + 'tested' => $remote['tested'] ?? '', + 'requires_php' => $remote['requires_php'] ?? '', + 'sections' => array( + 'description' => $remote['description'] ?? $this->plugin_data['Description'] ?? '', + 'changelog' => $remote['changelog'] ?? '', + ), + 'download_link' => $remote['download_url'] ?? '', + ); + } + + /** + * Get remote data with caching and HMAC signature verification. + * + * @return array Remote data. + */ + private function get_remote_data(): array { + $cached = get_site_transient( $this->cache_key ); + + // Verify HMAC signature if AUTH_SALT is defined and cache has signature. + if ( false !== $cached && defined( 'AUTH_SALT' ) && '' !== AUTH_SALT ) { + if ( is_array( $cached ) && isset( $cached['signature'], $cached['data'] ) ) { + $expected_sig = hash_hmac( 'sha256', $this->cache_key . serialize( $cached['data'] ), AUTH_SALT ); + + if ( hash_equals( $expected_sig, $cached['signature'] ) ) { + // Signature valid, return data. + return is_array( $cached['data'] ) ? $cached['data'] : array(); + } + + // Signature invalid, delete corrupted cache. + delete_site_transient( $this->cache_key ); + $cached = false; + } + } + + // If no valid cache, fetch fresh data. + if ( false === $cached ) { + $remote = $this->fetch_json(); + + // Store with HMAC signature if AUTH_SALT is available. + if ( defined( 'AUTH_SALT' ) && '' !== AUTH_SALT ) { + $payload = array( + 'data' => $remote ?: array(), + 'timestamp' => time(), + 'signature' => hash_hmac( 'sha256', $this->cache_key . serialize( $remote ?: array() ), AUTH_SALT ), + ); + set_site_transient( $this->cache_key, $payload, 6 * HOUR_IN_SECONDS ); + } else { + // Fallback to standard caching. + set_site_transient( $this->cache_key, $remote ?: array(), 6 * HOUR_IN_SECONDS ); + } + + return is_array( $remote ) ? $remote : array(); + } + + // Legacy cache format without signature (backward compatibility). + return is_array( $cached ) ? $cached : array(); + } + + /** + * Fetch JSON from remote URL. + * + * @return array Decoded JSON data. + */ + private function fetch_json(): array { + $response = wp_remote_get( + $this->json_url, + array( + 'timeout' => 10, + 'headers' => array( + 'Accept' => 'application/json', + ), + ) + ); + + if ( is_wp_error( $response ) ) { + return array(); + } + + $code = (int) wp_remote_retrieve_response_code( $response ); + if ( $code < 200 || $code >= 300 ) { + return array(); + } + + $body = wp_remote_retrieve_body( $response ); + $data = json_decode( $body, true ); + + return is_array( $data ) ? $data : array(); + } + + /** + * Check compatibility. + * + * @param array $remote Remote data. + * + * @return bool True if compatible. + */ + private function is_compatible( array $remote ): bool { + if ( ! empty( $remote['requires_php'] ) ) { + if ( version_compare( PHP_VERSION, $remote['requires_php'], '<' ) ) { + return false; + } + } + + if ( ! empty( $remote['requires'] ) ) { + if ( version_compare( get_bloginfo( 'version' ), $remote['requires'], '<' ) ) { + return false; + } + } + + return true; + } + + /** + * Handle manual cache clear via URL parameter. + */ + public function handle_cache_clear(): void { + // Check if this is a cache clear request first. + $clear_cache = filter_input( INPUT_GET, 'robotstxt_clear_update_cache', FILTER_UNSAFE_RAW ); + if ( null === $clear_cache ) { + return; + } + + // This is a cache clear request - now verify nonce. + $nonce_raw = filter_input( INPUT_GET, '_wpnonce', FILTER_UNSAFE_RAW ); + $nonce = $nonce_raw ? sanitize_text_field( wp_unslash( $nonce_raw ) ) : ''; + + if ( ! wp_verify_nonce( $nonce, 'robotstxt_clear_update_cache' ) ) { + wp_die( esc_html__( 'Security check failed', 'idrivee2-media-upload' ) ); + } + + // Check permissions. + if ( ! current_user_can( 'update_plugins' ) ) { + wp_die( esc_html__( 'You do not have sufficient permissions to access this page.', 'idrivee2-media-upload' ) ); + } + + $this->clear_cache(); + wp_safe_redirect( remove_query_arg( array( 'robotstxt_clear_update_cache', '_wpnonce' ) ) ); + exit; + } + + /** + * Clear update cache. + */ + public function clear_cache(): void { + delete_site_transient( $this->cache_key ); + delete_site_transient( 'update_plugins' ); + } + } +} diff --git a/update.json b/update.json new file mode 100644 index 0000000..9cd44ba --- /dev/null +++ b/update.json @@ -0,0 +1,27 @@ +{ + "name": "iDrivee2 Media Upload", + "slug": "idrivee2-media-upload", + "version": "1.1.1", + "download_url": "https://git.robotstxt.es/ROBOTSTXT/idrivee2-media-upload/releases/download/1.1.1/idrivee2-media-upload-1.1.1.zip", + "requires": "6.8", + "requires_php": "8.2", + "tested": "6.9", + "last_updated": "2026-02-04", + "author": "Javier Casares", + "author_profile": "https://www.javiercasares.com/", + "homepage": "https://git.robotstxt.es/ROBOTSTXT/idrivee2-media-upload", + "description": "Uploads media files to iDrivee2 (S3-compatible) with enterprise-grade security and logging.", + "changelog": "

1.1.1 - 2026-02-04

1.1.0 - 2026-02-04

1.0.0 - 2026-02-03

", + "sections": { + "description": "Uploads media files to iDrivee2 (S3-compatible) with enterprise-grade security and logging. The plugin intercepts WordPress media uploads, pushes files to an S3-compatible bucket, deletes local copies, and rewrites URLs to serve media from the CDN.", + "changelog": "

1.1.1 - 2026-02-04

1.1.0 - 2026-02-04

1.0.0 - 2026-02-03

" + }, + "banners": { + "low": "", + "high": "" + }, + "icons": { + "1x": "", + "2x": "" + } +}