This commit is contained in:
Javier Casares 2026-06-05 09:44:44 +00:00
commit a97911a978
6 changed files with 92 additions and 29 deletions

View file

@ -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_

View file

@ -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.

View file

@ -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<int, bool>
*/
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<string, mixed> 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<string, mixed> $meta Attachment metadata.
* @param int $attachment_id Attachment post ID.
* @return array<string, mixed> 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.
*

View file

@ -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_

View file

@ -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": "<h3>1.2.0 - 2026-06-02</h3><ul><li><strong>Performance:</strong> Concurrent S3 uploads via AWS CommandPool (default 5, tunable via IDRIVEE2_UPLOAD_CONCURRENCY)</li><li><strong>Performance:</strong> Stream files directly from disk — no full load into memory</li><li><strong>Performance:</strong> Removed per-file headObject pre-check — single batch DB write for stats</li><li><strong>Changed:</strong> Hook priority lowered from 999 to 10</li></ul><h3>1.1.4 - 2026-06-02</h3><ul><li><strong>Added:</strong> Full dev tooling: PHPCS, PHPStan level 9, PHPUnit test suite (22 tests)</li><li><strong>Fixed:</strong> WP_Filesystem null guard, type safety on get_option/get_transient, dynamic asset version</li><li><strong>Changed:</strong> Tested up to WordPress 7.1, PHP 8.1-8.5, Requires at least 4.1</li></ul><h3>1.1.3 - 2026-02-04</h3><ul><li><strong>Fixed:</strong> Critical namespace issue with Robotstxt_Updater class causing fatal error</li><li><strong>Fixed:</strong> Plugin now loads correctly without PHP fatal errors</li></ul><h3>1.1.2 - 2026-02-04</h3><ul><li><strong>Changed:</strong> Deployment script updated to use PHP 8.2 as platform base for production builds</li><li><strong>Changed:</strong> Now uses composer update --no-dev for consistent dependency resolution</li><li><strong>Improved:</strong> Production packages guarantee PHP 8.2+ compatibility regardless of dev environment</li></ul><h3>1.1.1 - 2026-02-04</h3><ul><li><strong>Fixed:</strong> Deployment script now includes essential files (update.json, robotstxt-updater.php, readme.txt, changelog.txt)</li><li><strong>Improved:</strong> Production packages now contain all files required for automatic updates from Gitea</li></ul><h3>1.1.0 - 2026-02-04</h3><ul><li><strong>Changed:</strong> Added explicit PHP version requirement (>=8.2) to composer.json</li><li><strong>Changed:</strong> Updated update.json with correct plugin information</li><li><strong>Changed:</strong> Fixed Text Domain in robotstxt-updater.php to match plugin slug</li><li><strong>Fixed:</strong> Composer now validates PHP version during dependency installation</li><li><strong>Fixed:</strong> Plugin update system correctly identifies the plugin</li><li><strong>Fixed:</strong> Translations properly loaded for updater error messages</li><li><strong>Improved:</strong> All text domains now consistently use 'idrivee2-media-upload'</li></ul><h3>1.0.0 - 2026-02-03</h3><ul><li><strong>Release:</strong> First stable release</li><li><strong>Feature:</strong> Automatic upload of media files to iDrivee2 (S3-compatible storage)</li><li><strong>Feature:</strong> URL rewriting to serve media from CDN</li><li><strong>Feature:</strong> Local file deletion after successful upload</li><li><strong>Feature:</strong> Admin interface with connection and upload testing</li><li><strong>Security:</strong> Enterprise-grade security with nonce validation</li><li><strong>Architecture:</strong> Class-based modular architecture with dependency injection</li><li><strong>Testing:</strong> PHPUnit test structure and PHPStan static analysis</li><li><strong>Compatibility:</strong> WordPress 6.8+ and PHP 8.2+</li></ul>",
"changelog": "<h3>1.2.1 - 2026-06-05</h3><ul><li><strong>Fixed:</strong> 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.</li><li><strong>Fixed:</strong> Fatal TypeError: fclose() on already-closed stream — AWS SDK closes streams after upload; added is_resource() check before fclose().</li></ul><h3>1.2.0 - 2026-06-02</h3><ul><li><strong>Performance:</strong> Concurrent S3 uploads via AWS CommandPool (default 5, tunable via IDRIVEE2_UPLOAD_CONCURRENCY)</li><li><strong>Performance:</strong> Stream files directly from disk — no full load into memory</li><li><strong>Performance:</strong> Removed per-file headObject pre-check — single batch DB write for stats</li><li><strong>Changed:</strong> Hook priority lowered from 999 to 10</li></ul><h3>1.1.4 - 2026-06-02</h3><ul><li><strong>Added:</strong> Full dev tooling: PHPCS, PHPStan level 9, PHPUnit test suite (22 tests)</li><li><strong>Fixed:</strong> WP_Filesystem null guard, type safety on get_option/get_transient, dynamic asset version</li><li><strong>Changed:</strong> Tested up to WordPress 7.1, PHP 8.1-8.5, Requires at least 4.1</li></ul><h3>1.1.3 - 2026-02-04</h3><ul><li><strong>Fixed:</strong> Critical namespace issue with Robotstxt_Updater class causing fatal error</li><li><strong>Fixed:</strong> Plugin now loads correctly without PHP fatal errors</li></ul><h3>1.1.2 - 2026-02-04</h3><ul><li><strong>Changed:</strong> Deployment script updated to use PHP 8.2 as platform base for production builds</li><li><strong>Changed:</strong> Now uses composer update --no-dev for consistent dependency resolution</li><li><strong>Improved:</strong> Production packages guarantee PHP 8.2+ compatibility regardless of dev environment</li></ul><h3>1.1.1 - 2026-02-04</h3><ul><li><strong>Fixed:</strong> Deployment script now includes essential files (update.json, robotstxt-updater.php, readme.txt, changelog.txt)</li><li><strong>Improved:</strong> Production packages now contain all files required for automatic updates from Gitea</li></ul><h3>1.1.0 - 2026-02-04</h3><ul><li><strong>Changed:</strong> Added explicit PHP version requirement (>=8.2) to composer.json</li><li><strong>Changed:</strong> Updated update.json with correct plugin information</li><li><strong>Changed:</strong> Fixed Text Domain in robotstxt-updater.php to match plugin slug</li><li><strong>Fixed:</strong> Composer now validates PHP version during dependency installation</li><li><strong>Fixed:</strong> Plugin update system correctly identifies the plugin</li><li><strong>Fixed:</strong> Translations properly loaded for updater error messages</li><li><strong>Improved:</strong> All text domains now consistently use 'idrivee2-media-upload'</li></ul><h3>1.0.0 - 2026-02-03</h3><ul><li><strong>Release:</strong> First stable release</li><li><strong>Feature:</strong> Automatic upload of media files to iDrivee2 (S3-compatible storage)</li><li><strong>Feature:</strong> URL rewriting to serve media from CDN</li><li><strong>Feature:</strong> Local file deletion after successful upload</li><li><strong>Feature:</strong> Admin interface with connection and upload testing</li><li><strong>Security:</strong> Enterprise-grade security with nonce validation</li><li><strong>Architecture:</strong> Class-based modular architecture with dependency injection</li><li><strong>Testing:</strong> PHPUnit test structure and PHPStan static analysis</li><li><strong>Compatibility:</strong> WordPress 6.8+ and PHP 8.2+</li></ul>",
"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": "<h3>1.2.0 - 2026-06-02</h3><ul><li><strong>Performance:</strong> Concurrent S3 uploads via AWS CommandPool (default 5, tunable via IDRIVEE2_UPLOAD_CONCURRENCY)</li><li><strong>Performance:</strong> Stream files directly from disk — no full load into memory</li><li><strong>Performance:</strong> Removed per-file headObject pre-check — single batch DB write for stats</li><li><strong>Changed:</strong> Hook priority lowered from 999 to 10</li></ul><h3>1.1.4 - 2026-06-02</h3><ul><li><strong>Added:</strong> Full dev tooling: PHPCS, PHPStan level 9, PHPUnit test suite (22 tests)</li><li><strong>Fixed:</strong> WP_Filesystem null guard, type safety on get_option/get_transient, dynamic asset version</li><li><strong>Changed:</strong> Tested up to WordPress 7.1, PHP 8.1-8.5, Requires at least 4.1</li></ul><h3>1.1.3 - 2026-02-04</h3><ul><li><strong>Fixed:</strong> Critical namespace issue with Robotstxt_Updater class causing fatal error</li><li><strong>Fixed:</strong> Plugin now loads correctly without PHP fatal errors</li></ul><h3>1.1.2 - 2026-02-04</h3><ul><li><strong>Changed:</strong> Deployment script updated to use PHP 8.2 as platform base for production builds</li><li><strong>Changed:</strong> Now uses composer update --no-dev for consistent dependency resolution</li><li><strong>Improved:</strong> Production packages guarantee PHP 8.2+ compatibility regardless of dev environment</li></ul><h3>1.1.1 - 2026-02-04</h3><ul><li><strong>Fixed:</strong> Deployment script now includes essential files (update.json, robotstxt-updater.php, readme.txt, changelog.txt)</li><li><strong>Improved:</strong> Production packages now contain all files required for automatic updates from Gitea</li></ul><h3>1.1.0 - 2026-02-04</h3><ul><li><strong>Changed:</strong> Added explicit PHP version requirement (>=8.2) to composer.json</li><li><strong>Changed:</strong> Updated update.json with correct plugin information</li><li><strong>Changed:</strong> Fixed Text Domain in robotstxt-updater.php to match plugin slug</li><li><strong>Fixed:</strong> Composer now validates PHP version during dependency installation</li><li><strong>Fixed:</strong> Plugin update system correctly identifies the plugin</li><li><strong>Fixed:</strong> Translations properly loaded for updater error messages</li><li><strong>Improved:</strong> All text domains now consistently use 'idrivee2-media-upload'</li></ul><h3>1.0.0 - 2026-02-03</h3><ul><li><strong>Release:</strong> First stable release</li><li><strong>Feature:</strong> Automatic upload of media files to iDrivee2 (S3-compatible storage)</li><li><strong>Feature:</strong> URL rewriting to serve media from CDN</li><li><strong>Feature:</strong> Local file deletion after successful upload</li><li><strong>Feature:</strong> Admin interface with connection and upload testing</li><li><strong>Security:</strong> Enterprise-grade security with nonce validation</li><li><strong>Architecture:</strong> Class-based modular architecture with dependency injection</li><li><strong>Testing:</strong> PHPUnit test structure and PHPStan static analysis</li><li><strong>Compatibility:</strong> WordPress 6.8+ and PHP 8.2+</li></ul>"
"changelog": "<h3>1.2.1 - 2026-06-05</h3><ul><li><strong>Fixed:</strong> 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.</li><li><strong>Fixed:</strong> Fatal TypeError: fclose() on already-closed stream — AWS SDK closes streams after upload; added is_resource() check before fclose().</li></ul><h3>1.2.0 - 2026-06-02</h3><ul><li><strong>Performance:</strong> Concurrent S3 uploads via AWS CommandPool (default 5, tunable via IDRIVEE2_UPLOAD_CONCURRENCY)</li><li><strong>Performance:</strong> Stream files directly from disk — no full load into memory</li><li><strong>Performance:</strong> Removed per-file headObject pre-check — single batch DB write for stats</li><li><strong>Changed:</strong> Hook priority lowered from 999 to 10</li></ul><h3>1.1.4 - 2026-06-02</h3><ul><li><strong>Added:</strong> Full dev tooling: PHPCS, PHPStan level 9, PHPUnit test suite (22 tests)</li><li><strong>Fixed:</strong> WP_Filesystem null guard, type safety on get_option/get_transient, dynamic asset version</li><li><strong>Changed:</strong> Tested up to WordPress 7.1, PHP 8.1-8.5, Requires at least 4.1</li></ul><h3>1.1.3 - 2026-02-04</h3><ul><li><strong>Fixed:</strong> Critical namespace issue with Robotstxt_Updater class causing fatal error</li><li><strong>Fixed:</strong> Plugin now loads correctly without PHP fatal errors</li></ul><h3>1.1.2 - 2026-02-04</h3><ul><li><strong>Changed:</strong> Deployment script updated to use PHP 8.2 as platform base for production builds</li><li><strong>Changed:</strong> Now uses composer update --no-dev for consistent dependency resolution</li><li><strong>Improved:</strong> Production packages guarantee PHP 8.2+ compatibility regardless of dev environment</li></ul><h3>1.1.1 - 2026-02-04</h3><ul><li><strong>Fixed:</strong> Deployment script now includes essential files (update.json, robotstxt-updater.php, readme.txt, changelog.txt)</li><li><strong>Improved:</strong> Production packages now contain all files required for automatic updates from Gitea</li></ul><h3>1.1.0 - 2026-02-04</h3><ul><li><strong>Changed:</strong> Added explicit PHP version requirement (>=8.2) to composer.json</li><li><strong>Changed:</strong> Updated update.json with correct plugin information</li><li><strong>Changed:</strong> Fixed Text Domain in robotstxt-updater.php to match plugin slug</li><li><strong>Fixed:</strong> Composer now validates PHP version during dependency installation</li><li><strong>Fixed:</strong> Plugin update system correctly identifies the plugin</li><li><strong>Fixed:</strong> Translations properly loaded for updater error messages</li><li><strong>Improved:</strong> All text domains now consistently use 'idrivee2-media-upload'</li></ul><h3>1.0.0 - 2026-02-03</h3><ul><li><strong>Release:</strong> First stable release</li><li><strong>Feature:</strong> Automatic upload of media files to iDrivee2 (S3-compatible storage)</li><li><strong>Feature:</strong> URL rewriting to serve media from CDN</li><li><strong>Feature:</strong> Local file deletion after successful upload</li><li><strong>Feature:</strong> Admin interface with connection and upload testing</li><li><strong>Security:</strong> Enterprise-grade security with nonce validation</li><li><strong>Architecture:</strong> Class-based modular architecture with dependency injection</li><li><strong>Testing:</strong> PHPUnit test structure and PHPStan static analysis</li><li><strong>Compatibility:</strong> WordPress 6.8+ and PHP 8.2+</li></ul>"
},
"banners": {
"low": "",

View file

@ -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(),