diff --git a/changelog.txt b/changelog.txt index af31936..c6de7c0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,27 @@ == Changelog == += 1.2.1 = + +_Release date: 2026-06-05_ + +**Fixed** + +* **Infinite recursion on image upload** — `wp_update_post()` (called to update the attachment GUID after S3 upload) was firing the `edit_attachment` WordPress action, which re-triggered the upload method, which called `wp_update_post()` again, creating infinite recursion. Xdebug killed the process at 512 stack frames, WordPress reported "The server cannot process the image". Fixed by: (1) removing the `edit_attachment` hook — `wp_update_attachment_metadata` covers all new-upload scenarios; (2) adding a static re-entry guard (`$in_progress` per attachment ID) as a safety net. + +* **Fatal error: fclose() on already-closed stream** — The AWS SDK (via Guzzle) closes file streams automatically after reading them for upload. The `finally` block was attempting to `fclose()` streams that were already closed, throwing `TypeError: fclose(): Argument #1 ($stream) must be an open stream resource`. Fixed by checking `is_resource($fh)` before calling `fclose()`. + +**Compatibility** + +* WordPress: 4.1 - 7.1 +* PHP: 8.1 - 8.5 + +**Tests** + +* PHP Coding Standards: 3.13.5 (0 errors) +* WordPress Coding Standards: 3.3.0 (0 violations) +* PHPStan: Level 9, 0 errors +* PHPUnit: 22 tests, 54 assertions + = 1.2.0 = _Release date: 2026-06-02_ diff --git a/idrivee2-media-upload.php b/idrivee2-media-upload.php index 74cf5c9..33ebdaf 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.2.0 + * Version: 1.2.1 * Requires at least: 4.1 * Requires PHP: 8.1 * Author: ROBOTSTXT @@ -36,7 +36,7 @@ if ( ! defined( 'ABSPATH' ) ) { * * @since 1.1.4 */ -define( 'IDRIVEE2_MEDIA_VERSION', '1.2.0' ); +define( 'IDRIVEE2_MEDIA_VERSION', '1.2.1' ); /** * Load Composer autoloader if available. diff --git a/includes/class-media-uploader.php b/includes/class-media-uploader.php index b7f653d..f1cae94 100644 --- a/includes/class-media-uploader.php +++ b/includes/class-media-uploader.php @@ -26,6 +26,16 @@ if ( ! defined( 'ABSPATH' ) ) { * @since 0.3.0 */ class Media_Uploader { + /** + * Attachment IDs currently being uploaded, to prevent re-entrant calls. + * + * wp_update_post() (used to update the attachment GUID) fires edit_attachment, + * which would re-trigger upload_attachment_to_idrivee2() causing infinite recursion. + * + * @var array + */ + private static array $in_progress = array(); + /** * Configuration instance. * @@ -71,7 +81,6 @@ class Media_Uploader { */ public function register(): void { add_filter( 'wp_update_attachment_metadata', array( $this, 'upload_attachment_to_idrivee2' ), 10, 2 ); - add_action( 'edit_attachment', array( $this, 'handle_edit_attachment' ) ); add_action( 'idrivee2_cleanup_local_files', array( $this, 'cleanup_local_files' ) ); @@ -97,6 +106,30 @@ class Media_Uploader { * @return array Unchanged metadata array. */ public function upload_attachment_to_idrivee2( array $meta, int $attachment_id ): array { + // Guard against re-entrant calls: wp_update_post() (used to update the GUID) + // fires edit_attachment which would recurse back into this method. + if ( isset( self::$in_progress[ $attachment_id ] ) ) { + return $meta; + } + self::$in_progress[ $attachment_id ] = true; + + try { + return $this->do_upload( $meta, $attachment_id ); + } finally { + unset( self::$in_progress[ $attachment_id ] ); + } + } + + /** + * Internal upload implementation, called only when not already in progress. + * + * @since 1.2.0 + * + * @param array $meta Attachment metadata. + * @param int $attachment_id Attachment post ID. + * @return array Unchanged metadata array. + */ + private function do_upload( array $meta, int $attachment_id ): array { if ( ! $this->config->is_configured() ) { return $meta; } @@ -253,9 +286,11 @@ class Media_Uploader { try { $pool->promise()->wait(); } finally { - // Always close file streams, even if wait() throws. + // The SDK closes streams after upload; only close those still open. foreach ( $handles as $fh ) { - fclose( $fh ); + if ( is_resource( $fh ) ) { + fclose( $fh ); + } } } @@ -301,21 +336,6 @@ class Media_Uploader { return $meta; } - /** - * Handle edit_attachment action as a fallback. - * - * @since 0.3.0 - * - * @param int $post_id Attachment post ID. - * @return void - */ - public function handle_edit_attachment( int $post_id ): void { - $meta = wp_get_attachment_metadata( $post_id ); - if ( $meta ) { - $this->upload_attachment_to_idrivee2( $meta, $post_id ); - } - } - /** * Schedule files for deletion via the cron queue. * diff --git a/readme.txt b/readme.txt index b1ec8b4..6b3dce8 100644 --- a/readme.txt +++ b/readme.txt @@ -3,9 +3,9 @@ Contributors: robotstxt, javiercasares Tags: media, upload, s3, cdn, storage, idrivee2, cloud Requires at least: 4.1 Tested up to: 7.1 -Stable tag: 1.2.0 +Stable tag: 1.2.1 Requires PHP: 8.1 -Version: 1.2.0 +Version: 1.2.1 License: GPL-3.0-or-later License URI: https://www.gnu.org/licenses/gpl-3.0.txt @@ -199,6 +199,27 @@ PHP 8.2 or higher is required. The plugin uses strict type declarations and is t == Changelog == += 1.2.1 = + +_Release date: 2026-06-05_ + +**Fixed** + +* **Infinite recursion on image upload** — `wp_update_post()` (used to update the attachment GUID to the S3/CDN URL) was firing the `edit_attachment` WordPress action, which re-triggered the upload method, causing infinite recursion. WordPress reported "The server cannot process the image." Fixed by removing the `edit_attachment` hook (unnecessary — `wp_update_attachment_metadata` covers all new-upload scenarios) and adding a per-attachment re-entry guard. +* **Fatal error: `fclose()` on already-closed stream** — The AWS SDK closes file streams automatically after upload. The cleanup block was calling `fclose()` on already-closed streams, throwing a `TypeError`. Fixed by checking `is_resource()` before closing. + +**Compatibility** + +* WordPress: 4.1 - 7.1 +* PHP: 8.1 - 8.5 + +**Tests** + +* PHP Coding Standards: 3.13.5 (0 errors) +* WordPress Coding Standards: 3.3.0 (0 violations) +* PHPStan: Level 9, 0 errors +* PHPUnit: 22 tests, 54 assertions + = 1.2.0 = _Release date: 2026-06-02_ diff --git a/update.json b/update.json index 607df8c..2eb039f 100644 --- a/update.json +++ b/update.json @@ -1,20 +1,20 @@ { "name": "iDrivee2 Media Upload", "slug": "idrivee2-media-upload", - "version": "1.2.0", - "download_url": "https://git.robotstxt.es/ROBOTSTXT/idrivee2-media-upload/releases/download/1.2.0/idrivee2-media-upload-1.2.0.zip", + "version": "1.2.1", + "download_url": "https://git.robotstxt.es/ROBOTSTXT/idrivee2-media-upload/releases/download/1.2.1/idrivee2-media-upload-1.2.1.zip", "requires": "4.1", "requires_php": "8.1", "tested": "7.1", - "last_updated": "2026-06-02", + "last_updated": "2026-06-05", "author": "ROBOTSTXT", "author_profile": "https://www.robotstxt.es/", "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.2.0 - 2026-06-02

  • Performance: Concurrent S3 uploads via AWS CommandPool (default 5, tunable via IDRIVEE2_UPLOAD_CONCURRENCY)
  • Performance: Stream files directly from disk — no full load into memory
  • Performance: Removed per-file headObject pre-check — single batch DB write for stats
  • Changed: Hook priority lowered from 999 to 10

1.1.4 - 2026-06-02

  • Added: Full dev tooling: PHPCS, PHPStan level 9, PHPUnit test suite (22 tests)
  • Fixed: WP_Filesystem null guard, type safety on get_option/get_transient, dynamic asset version
  • Changed: Tested up to WordPress 7.1, PHP 8.1-8.5, Requires at least 4.1

1.1.3 - 2026-02-04

  • Fixed: Critical namespace issue with Robotstxt_Updater class causing fatal error
  • Fixed: Plugin now loads correctly without PHP fatal errors

1.1.2 - 2026-02-04

  • Changed: Deployment script updated to use PHP 8.2 as platform base for production builds
  • Changed: Now uses composer update --no-dev for consistent dependency resolution
  • Improved: Production packages guarantee PHP 8.2+ compatibility regardless of dev environment

1.1.1 - 2026-02-04

  • Fixed: Deployment script now includes essential files (update.json, robotstxt-updater.php, readme.txt, changelog.txt)
  • Improved: Production packages now contain all files required for automatic updates from Gitea

1.1.0 - 2026-02-04

  • Changed: Added explicit PHP version requirement (>=8.2) to composer.json
  • Changed: Updated update.json with correct plugin information
  • Changed: Fixed Text Domain in robotstxt-updater.php to match plugin slug
  • Fixed: Composer now validates PHP version during dependency installation
  • Fixed: Plugin update system correctly identifies the plugin
  • Fixed: Translations properly loaded for updater error messages
  • Improved: All text domains now consistently use 'idrivee2-media-upload'

1.0.0 - 2026-02-03

  • Release: First stable release
  • Feature: Automatic upload of media files to iDrivee2 (S3-compatible storage)
  • Feature: URL rewriting to serve media from CDN
  • Feature: Local file deletion after successful upload
  • Feature: Admin interface with connection and upload testing
  • Security: Enterprise-grade security with nonce validation
  • Architecture: Class-based modular architecture with dependency injection
  • Testing: PHPUnit test structure and PHPStan static analysis
  • Compatibility: WordPress 6.8+ and PHP 8.2+
", + "changelog": "

1.2.1 - 2026-06-05

  • Fixed: Infinite recursion on image upload — wp_update_post() fired edit_attachment which re-triggered the upload method. Removed edit_attachment hook, added re-entry guard.
  • Fixed: Fatal TypeError: fclose() on already-closed stream — AWS SDK closes streams after upload; added is_resource() check before fclose().

1.2.0 - 2026-06-02

  • Performance: Concurrent S3 uploads via AWS CommandPool (default 5, tunable via IDRIVEE2_UPLOAD_CONCURRENCY)
  • Performance: Stream files directly from disk — no full load into memory
  • Performance: Removed per-file headObject pre-check — single batch DB write for stats
  • Changed: Hook priority lowered from 999 to 10

1.1.4 - 2026-06-02

  • Added: Full dev tooling: PHPCS, PHPStan level 9, PHPUnit test suite (22 tests)
  • Fixed: WP_Filesystem null guard, type safety on get_option/get_transient, dynamic asset version
  • Changed: Tested up to WordPress 7.1, PHP 8.1-8.5, Requires at least 4.1

1.1.3 - 2026-02-04

  • Fixed: Critical namespace issue with Robotstxt_Updater class causing fatal error
  • Fixed: Plugin now loads correctly without PHP fatal errors

1.1.2 - 2026-02-04

  • Changed: Deployment script updated to use PHP 8.2 as platform base for production builds
  • Changed: Now uses composer update --no-dev for consistent dependency resolution
  • Improved: Production packages guarantee PHP 8.2+ compatibility regardless of dev environment

1.1.1 - 2026-02-04

  • Fixed: Deployment script now includes essential files (update.json, robotstxt-updater.php, readme.txt, changelog.txt)
  • Improved: Production packages now contain all files required for automatic updates from Gitea

1.1.0 - 2026-02-04

  • Changed: Added explicit PHP version requirement (>=8.2) to composer.json
  • Changed: Updated update.json with correct plugin information
  • Changed: Fixed Text Domain in robotstxt-updater.php to match plugin slug
  • Fixed: Composer now validates PHP version during dependency installation
  • Fixed: Plugin update system correctly identifies the plugin
  • Fixed: Translations properly loaded for updater error messages
  • Improved: All text domains now consistently use 'idrivee2-media-upload'

1.0.0 - 2026-02-03

  • Release: First stable release
  • Feature: Automatic upload of media files to iDrivee2 (S3-compatible storage)
  • Feature: URL rewriting to serve media from CDN
  • Feature: Local file deletion after successful upload
  • Feature: Admin interface with connection and upload testing
  • Security: Enterprise-grade security with nonce validation
  • Architecture: Class-based modular architecture with dependency injection
  • Testing: PHPUnit test structure and PHPStan static analysis
  • Compatibility: WordPress 6.8+ and PHP 8.2+
", "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.2.0 - 2026-06-02

  • Performance: Concurrent S3 uploads via AWS CommandPool (default 5, tunable via IDRIVEE2_UPLOAD_CONCURRENCY)
  • Performance: Stream files directly from disk — no full load into memory
  • Performance: Removed per-file headObject pre-check — single batch DB write for stats
  • Changed: Hook priority lowered from 999 to 10

1.1.4 - 2026-06-02

  • Added: Full dev tooling: PHPCS, PHPStan level 9, PHPUnit test suite (22 tests)
  • Fixed: WP_Filesystem null guard, type safety on get_option/get_transient, dynamic asset version
  • Changed: Tested up to WordPress 7.1, PHP 8.1-8.5, Requires at least 4.1

1.1.3 - 2026-02-04

  • Fixed: Critical namespace issue with Robotstxt_Updater class causing fatal error
  • Fixed: Plugin now loads correctly without PHP fatal errors

1.1.2 - 2026-02-04

  • Changed: Deployment script updated to use PHP 8.2 as platform base for production builds
  • Changed: Now uses composer update --no-dev for consistent dependency resolution
  • Improved: Production packages guarantee PHP 8.2+ compatibility regardless of dev environment

1.1.1 - 2026-02-04

  • Fixed: Deployment script now includes essential files (update.json, robotstxt-updater.php, readme.txt, changelog.txt)
  • Improved: Production packages now contain all files required for automatic updates from Gitea

1.1.0 - 2026-02-04

  • Changed: Added explicit PHP version requirement (>=8.2) to composer.json
  • Changed: Updated update.json with correct plugin information
  • Changed: Fixed Text Domain in robotstxt-updater.php to match plugin slug
  • Fixed: Composer now validates PHP version during dependency installation
  • Fixed: Plugin update system correctly identifies the plugin
  • Fixed: Translations properly loaded for updater error messages
  • Improved: All text domains now consistently use 'idrivee2-media-upload'

1.0.0 - 2026-02-03

  • Release: First stable release
  • Feature: Automatic upload of media files to iDrivee2 (S3-compatible storage)
  • Feature: URL rewriting to serve media from CDN
  • Feature: Local file deletion after successful upload
  • Feature: Admin interface with connection and upload testing
  • Security: Enterprise-grade security with nonce validation
  • Architecture: Class-based modular architecture with dependency injection
  • Testing: PHPUnit test structure and PHPStan static analysis
  • Compatibility: WordPress 6.8+ and PHP 8.2+
" + "changelog": "

1.2.1 - 2026-06-05

  • Fixed: Infinite recursion on image upload — wp_update_post() fired edit_attachment which re-triggered the upload method. Removed edit_attachment hook, added re-entry guard.
  • Fixed: Fatal TypeError: fclose() on already-closed stream — AWS SDK closes streams after upload; added is_resource() check before fclose().

1.2.0 - 2026-06-02

  • Performance: Concurrent S3 uploads via AWS CommandPool (default 5, tunable via IDRIVEE2_UPLOAD_CONCURRENCY)
  • Performance: Stream files directly from disk — no full load into memory
  • Performance: Removed per-file headObject pre-check — single batch DB write for stats
  • Changed: Hook priority lowered from 999 to 10

1.1.4 - 2026-06-02

  • Added: Full dev tooling: PHPCS, PHPStan level 9, PHPUnit test suite (22 tests)
  • Fixed: WP_Filesystem null guard, type safety on get_option/get_transient, dynamic asset version
  • Changed: Tested up to WordPress 7.1, PHP 8.1-8.5, Requires at least 4.1

1.1.3 - 2026-02-04

  • Fixed: Critical namespace issue with Robotstxt_Updater class causing fatal error
  • Fixed: Plugin now loads correctly without PHP fatal errors

1.1.2 - 2026-02-04

  • Changed: Deployment script updated to use PHP 8.2 as platform base for production builds
  • Changed: Now uses composer update --no-dev for consistent dependency resolution
  • Improved: Production packages guarantee PHP 8.2+ compatibility regardless of dev environment

1.1.1 - 2026-02-04

  • Fixed: Deployment script now includes essential files (update.json, robotstxt-updater.php, readme.txt, changelog.txt)
  • Improved: Production packages now contain all files required for automatic updates from Gitea

1.1.0 - 2026-02-04

  • Changed: Added explicit PHP version requirement (>=8.2) to composer.json
  • Changed: Updated update.json with correct plugin information
  • Changed: Fixed Text Domain in robotstxt-updater.php to match plugin slug
  • Fixed: Composer now validates PHP version during dependency installation
  • Fixed: Plugin update system correctly identifies the plugin
  • Fixed: Translations properly loaded for updater error messages
  • Improved: All text domains now consistently use 'idrivee2-media-upload'

1.0.0 - 2026-02-03

  • Release: First stable release
  • Feature: Automatic upload of media files to iDrivee2 (S3-compatible storage)
  • Feature: URL rewriting to serve media from CDN
  • Feature: Local file deletion after successful upload
  • Feature: Admin interface with connection and upload testing
  • Security: Enterprise-grade security with nonce validation
  • Architecture: Class-based modular architecture with dependency injection
  • Testing: PHPUnit test structure and PHPStan static analysis
  • Compatibility: WordPress 6.8+ and PHP 8.2+
" }, "banners": { "low": "", diff --git a/vendor/composer/installed.php b/vendor/composer/installed.php index ba21af0..c71af24 100644 --- a/vendor/composer/installed.php +++ b/vendor/composer/installed.php @@ -3,7 +3,7 @@ 'name' => 'robotstxt/idrivee2-media-upload', 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => '51ad14453c553b99ed1481fcaf638827864adc1d', + 'reference' => '71da5f1fe1589fefd2386f9bcc9fa268f83b4049', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(), @@ -121,7 +121,7 @@ 'robotstxt/idrivee2-media-upload' => array( 'pretty_version' => 'dev-main', 'version' => 'dev-main', - 'reference' => '51ad14453c553b99ed1481fcaf638827864adc1d', + 'reference' => '71da5f1fe1589fefd2386f9bcc9fa268f83b4049', 'type' => 'wordpress-plugin', 'install_path' => __DIR__ . '/../../', 'aliases' => array(),