This commit is contained in:
Javier Casares 2026-07-18 13:29:38 +00:00
commit 916d7845be
91 changed files with 6861 additions and 6907 deletions

View file

@ -526,6 +526,11 @@ class Admin_Page {
$sanitized['region'] = isset( $input['region'] ) ? sanitize_text_field( $input['region'] ) : '';
$sanitized['domain'] = isset( $input['domain'] ) ? sanitize_text_field( $input['domain'] ) : '';
// Sub-sizes mode: strict whitelist, default 'all'.
$raw_mode = isset( $input['subsizes_mode'] ) ? strtolower( (string) sanitize_text_field( $input['subsizes_mode'] ) ) : '';
$allowed_modes = array( 'all', 'thumbnail', 'none' );
$sanitized['subsizes_mode'] = in_array( $raw_mode, $allowed_modes, true ) ? $raw_mode : 'all';
return $sanitized;
}
@ -565,24 +570,26 @@ class Admin_Page {
* @return void
*/
public function render_settings_page(): void {
$host = $this->config->get_host();
$access_key = $this->config->get_key();
$secret = $this->config->get_secret();
$bucket = $this->config->get_bucket();
$region = $this->config->get_region();
$domain = $this->config->get_domain();
$host = $this->config->get_host();
$access_key = $this->config->get_key();
$secret = $this->config->get_secret();
$bucket = $this->config->get_bucket();
$region = $this->config->get_region();
$domain = $this->config->get_domain();
$subsizes_mode = $this->config->get_subsizes_mode();
$is_configured = $this->config->is_configured();
// Check which fields are defined in wp-config.php.
$host_in_config = $this->config->is_defined_in_wp_config( 'host' );
$key_in_config = $this->config->is_defined_in_wp_config( 'key' );
$secret_in_config = $this->config->is_defined_in_wp_config( 'secret' );
$bucket_in_config = $this->config->is_defined_in_wp_config( 'bucket' );
$region_in_config = $this->config->is_defined_in_wp_config( 'region' );
$domain_in_config = $this->config->is_defined_in_wp_config( 'domain' );
$host_in_config = $this->config->is_defined_in_wp_config( 'host' );
$key_in_config = $this->config->is_defined_in_wp_config( 'key' );
$secret_in_config = $this->config->is_defined_in_wp_config( 'secret' );
$bucket_in_config = $this->config->is_defined_in_wp_config( 'bucket' );
$region_in_config = $this->config->is_defined_in_wp_config( 'region' );
$domain_in_config = $this->config->is_defined_in_wp_config( 'domain' );
$subsizes_mode_in_config = $this->config->is_defined_in_wp_config( 'subsizes_mode' );
$any_in_config = $host_in_config || $key_in_config || $secret_in_config || $bucket_in_config || $region_in_config || $domain_in_config;
$any_in_config = $host_in_config || $key_in_config || $secret_in_config || $bucket_in_config || $region_in_config || $domain_in_config || $subsizes_mode_in_config;
?>
<div class="wrap">
<h1><?php esc_html_e( 'iDrivee2 Media Upload Settings', 'idrivee2-media-upload' ); ?></h1>
@ -731,32 +738,80 @@ class Admin_Page {
</td>
</tr>
<tr>
<th scope="row">
<label for="idrivee2_domain"><?php esc_html_e( 'Custom CDN Domain', 'idrivee2-media-upload' ); ?></label>
</th>
<td>
<input
type="url"
id="idrivee2_domain"
name="idrivee2_media_settings[domain]"
value="<?php echo esc_attr( $domain ); ?>"
class="regular-text"
placeholder="https://cdn.example.com"
<?php echo $domain_in_config ? 'readonly' : ''; ?>
/>
<p class="description">
<?php
if ( $domain_in_config ) {
esc_html_e( 'Defined in wp-config.php (IDRIVEE2_MEDIA_DOMAIN)', 'idrivee2-media-upload' );
} else {
esc_html_e( 'Optional: Custom domain for serving media files.', 'idrivee2-media-upload' );
}
?>
</p>
</td>
</tr>
</tbody>
<tr>
<th scope="row">
<label for="idrivee2_domain"><?php esc_html_e( 'Custom CDN Domain', 'idrivee2-media-upload' ); ?></label>
</th>
<td>
<input
type="url"
id="idrivee2_domain"
name="idrivee2_media_settings[domain]"
value="<?php echo esc_attr( $domain ); ?>"
class="regular-text"
placeholder="https://cdn.example.com"
<?php echo $domain_in_config ? 'readonly' : ''; ?>
/>
<p class="description">
<?php
if ( $domain_in_config ) {
esc_html_e( 'Defined in wp-config.php (IDRIVEE2_MEDIA_DOMAIN)', 'idrivee2-media-upload' );
} else {
esc_html_e( 'Optional: Custom domain for serving media files.', 'idrivee2-media-upload' );
}
?>
</p>
</td>
</tr>
<tr>
<th scope="row">
<label for="idrivee2_subsizes_mode"><?php esc_html_e( 'Image Sub-sizes', 'idrivee2-media-upload' ); ?></label>
</th>
<td>
<select
id="idrivee2_subsizes_mode"
name="idrivee2_media_settings[subsizes_mode]"
<?php echo $subsizes_mode_in_config ? 'disabled' : ''; ?>
>
<?php
$mode_options = array(
'all' => __( 'All registered sizes (WordPress default)', 'idrivee2-media-upload' ),
'thumbnail' => __( 'Only thumbnail (faster uploads)', 'idrivee2-media-upload' ),
'none' => __( 'No sub-sizes (original only)', 'idrivee2-media-upload' ),
);
foreach ( $mode_options as $value => $label ) {
printf(
'<option value="%s"%s>%s</option>',
esc_attr( $value ),
selected( $subsizes_mode, $value, false ),
esc_html( $label )
);
}
?>
</select>
<?php
// Disabled selects are not submitted with the form; preserve the
// constant-defined value via a hidden input so it is not lost on save.
if ( $subsizes_mode_in_config ) {
printf(
'<input type="hidden" name="idrivee2_media_settings[subsizes_mode]" value="%s" />',
esc_attr( $subsizes_mode )
);
}
?>
<p class="description">
<?php
if ( $subsizes_mode_in_config ) {
esc_html_e( 'Defined in wp-config.php (IDRIVEE2_MEDIA_SUBSIZES_MODE)', 'idrivee2-media-upload' );
} else {
esc_html_e( 'Reduces work for large uploads (e.g. camera files). Also disables the -scaled derivative in thumbnail/none modes.', 'idrivee2-media-upload' );
}
?>
</p>
</td>
</tr>
</tbody>
</table>
<?php if ( ! $any_in_config ) : ?>
@ -767,7 +822,7 @@ class Admin_Page {
</p>
<?php
// Show submit button only if at least one field can be edited.
$can_edit = ! $host_in_config || ! $key_in_config || ! $secret_in_config || ! $bucket_in_config || ! $region_in_config || ! $domain_in_config;
$can_edit = ! $host_in_config || ! $key_in_config || ! $secret_in_config || ! $bucket_in_config || ! $region_in_config || ! $domain_in_config || ! $subsizes_mode_in_config;
if ( $can_edit ) :
?>
<?php submit_button( __( 'Save Settings', 'idrivee2-media-upload' ) ); ?>

View file

@ -16,15 +16,15 @@ if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Configuration validator and accessor for iDrivee2 constants.
*
* Validates and provides access to the five required configuration constants
* and the optional CDN domain constant. Configuration can come from either
* wp-config.php constants (priority) or WordPress options.
*
* @since 0.3.0
*/
/**
* Configuration validator and accessor for iDrivee2 constants.
*
* Validates and provides access to the five required configuration constants
* and the optional CDN domain constant. Configuration can come from either
* wp-config.php constants (priority) or WordPress options.
*
* @since 0.3.0
*/
class Config {
/**
* Required configuration keys.
@ -46,6 +46,25 @@ class Config {
*/
private const OPTION_NAME = 'idrivee2_media_settings';
/**
* Map of config keys to wp-config.php constant names.
*
* Centralised so is_defined_in_wp_config() and get_value() cannot drift.
*
* @since 1.3.0
*
* @var array<string, string>
*/
private const CONSTANT_MAP = array(
'host' => 'IDRIVEE2_MEDIA_HOST',
'key' => 'IDRIVEE2_MEDIA_KEY',
'secret' => 'IDRIVEE2_MEDIA_SECRET',
'bucket' => 'IDRIVEE2_MEDIA_BUCKET',
'region' => 'IDRIVEE2_MEDIA_REGION',
'domain' => 'IDRIVEE2_MEDIA_DOMAIN',
'subsizes_mode' => 'IDRIVEE2_MEDIA_SUBSIZES_MODE',
);
/**
* Logger instance.
*
@ -92,17 +111,8 @@ class Config {
* @return bool True if defined in wp-config.php, false otherwise.
*/
public function is_defined_in_wp_config( string $key ): bool {
$constant_map = array(
'host' => 'IDRIVEE2_MEDIA_HOST',
'key' => 'IDRIVEE2_MEDIA_KEY',
'secret' => 'IDRIVEE2_MEDIA_SECRET',
'bucket' => 'IDRIVEE2_MEDIA_BUCKET',
'region' => 'IDRIVEE2_MEDIA_REGION',
'domain' => 'IDRIVEE2_MEDIA_DOMAIN',
);
$constant_name = $constant_map[ $key ] ?? '';
return $constant_name && defined( $constant_name );
$constant_name = self::CONSTANT_MAP[ $key ] ?? '';
return '' !== $constant_name && defined( $constant_name );
}
/**
@ -116,18 +126,9 @@ class Config {
* @return string The configuration value, or empty string if not set.
*/
private function get_value( string $key ): string {
$constant_map = array(
'host' => 'IDRIVEE2_MEDIA_HOST',
'key' => 'IDRIVEE2_MEDIA_KEY',
'secret' => 'IDRIVEE2_MEDIA_SECRET',
'bucket' => 'IDRIVEE2_MEDIA_BUCKET',
'region' => 'IDRIVEE2_MEDIA_REGION',
'domain' => 'IDRIVEE2_MEDIA_DOMAIN',
);
// Check wp-config.php constant first (highest priority).
$constant_name = $constant_map[ $key ] ?? '';
if ( $constant_name && defined( $constant_name ) ) {
$constant_name = self::CONSTANT_MAP[ $key ] ?? '';
if ( '' !== $constant_name && defined( $constant_name ) ) {
return (string) constant( $constant_name );
}
@ -216,6 +217,29 @@ class Config {
return ! empty( $this->get_domain() );
}
/**
* Get the configured sub-sizes mode.
*
* Controls which image sub-sizes WordPress generates. Returns one of
* 'all', 'thumbnail', or 'none'; falls back to 'all' for any value that
* is not on the whitelist (including unset/empty).
*
* @since 1.3.0
*
* @return string The sub-sizes mode.
*/
public function get_subsizes_mode(): string {
$value = $this->get_value( 'subsizes_mode' );
$allowed = array(
'all',
'thumbnail',
'none',
);
return in_array( $value, $allowed, true ) ? $value : 'all';
}
/**
* Update configuration options in WordPress database.
*
@ -239,6 +263,11 @@ class Config {
'domain' => isset( $data['domain'] ) ? sanitize_text_field( $data['domain'] ) : '',
);
// Sub-sizes mode: strict whitelist, default 'all'.
$raw_mode = isset( $data['subsizes_mode'] ) ? strtolower( (string) sanitize_text_field( $data['subsizes_mode'] ) ) : '';
$allowed_modes = array( 'all', 'thumbnail', 'none' );
$options['subsizes_mode'] = in_array( $raw_mode, $allowed_modes, true ) ? $raw_mode : 'all';
// Log configuration changes.
if ( $this->logger ) {
foreach ( $options as $key => $new_value ) {

View file

@ -29,7 +29,7 @@ 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,
* Calling wp_update_post() to update the attachment GUID fires edit_attachment,
* which would re-trigger upload_attachment_to_idrivee2() causing infinite recursion.
*
* @var array<int, bool>
@ -145,7 +145,7 @@ class Media_Uploader {
$base_path = path_join( $basedir, $meta_file );
$meta_sizes = isset( $meta['sizes'] ) && is_array( $meta['sizes'] ) ? $meta['sizes'] : array();
/** @var array<string, string> $files */
/* @var array<string, string> $files */
$files = array( 'original' => $base_path );
foreach ( $meta_sizes as $size ) {
if ( is_array( $size ) && isset( $size['file'] ) && is_string( $size['file'] ) ) {
@ -157,8 +157,8 @@ class Media_Uploader {
$client = $this->client_factory->create();
$bucket = $this->config->get_bucket();
$commands = array();
$key_map = array(); // int index -> file metadata
$handles = array(); // int index -> resource
$key_map = array(); // int index -> file metadata.
$handles = array(); // int index -> resource.
$real_basedir = realpath( $basedir );
$idx = 0;

View file

@ -81,6 +81,13 @@ class Plugin {
*/
private $url_rewriter;
/**
* Sub-size filter.
*
* @var Subsize_Filter
*/
private $subsize_filter;
/**
* Plugin file path.
*
@ -110,6 +117,7 @@ class Plugin {
$this->admin_page = new Admin_Page( $this->config, $this->client_factory, $this->logger, $this->rate_limiter, $plugin_file );
$this->media_uploader = new Media_Uploader( $this->config, $this->client_factory, $this->logger );
$this->url_rewriter = new URL_Rewriter( $this->config );
$this->subsize_filter = new Subsize_Filter( $this->config );
}
/**
@ -145,6 +153,7 @@ class Plugin {
$this->admin_page->register();
$this->media_uploader->register();
$this->url_rewriter->register();
$this->subsize_filter->register();
}
/**

View file

@ -0,0 +1,168 @@
<?php
/**
* Sub-size filter for iDrivee2 Media Upload.
*
* @package iDrivee2Media
* @since 1.3.0
*/
declare(strict_types=1);
namespace iDrivee2Media;
/**
* Prevent direct access to this file.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit;
}
/**
* Filters the image sub-sizes generated by WordPress.
*
* Reduces the number of sub-sizes generated per uploaded image. Affects both
* the WP 7.1 client-side path (the browser reads the registered sub-sizes via
* the REST index) and the traditional server-side path
* (intermediate_image_sizes_advanced consumes the same list).
*
* Modes:
* - 'all' Default WordPress behavior; all registered sizes are generated.
* - 'thumbnail' Only the 'thumbnail' sub-size is generated.
* - 'none' No sub-sizes are generated; only the original is kept.
*
* In 'thumbnail' and 'none' modes the big_image_size_threshold is also
* disabled, so WordPress does not create a '-scaled' derivative for images
* larger than 2560px.
*
* @since 1.3.0
*/
class Subsize_Filter {
/**
* Mode: generate all registered sub-sizes (default).
*
* @var string
*/
public const MODE_ALL = 'all';
/**
* Mode: generate only the 'thumbnail' sub-size.
*
* @var string
*/
public const MODE_THUMBNAIL = 'thumbnail';
/**
* Mode: generate no sub-sizes.
*
* @var string
*/
public const MODE_NONE = 'none';
/**
* Allowed mode values.
*
* @var array<int, string>
*/
private const ALLOWED_MODES = array(
self::MODE_ALL,
self::MODE_THUMBNAIL,
self::MODE_NONE,
);
/**
* Configuration instance.
*
* @var Config
*/
private $config;
/**
* Constructor.
*
* @since 1.3.0
*
* @param Config $config Configuration instance.
*/
public function __construct( Config $config ) {
$this->config = $config;
}
/**
* Register the WordPress filters.
*
* Runs at priority 90 so user-supplied filters at the default priority
* (10) and at WP core's own guard priority (100) are not pre-empted.
*
* @since 1.3.0
*
* @return void
*/
public function register(): void {
add_filter( 'intermediate_image_sizes', array( $this, 'filter_intermediate_image_sizes' ), 90 );
add_filter( 'big_image_size_threshold', array( $this, 'filter_big_image_threshold' ), 90, 4 );
}
/**
* Filter the list of intermediate image sizes.
*
* This filter is the upstream source of truth consumed by
* wp_get_registered_image_subsizes(), which feeds both the REST index
* response (client-side path) and intermediate_image_sizes_advanced
* (server-side path).
*
* @since 1.3.0
*
* @param array<int, string> $sizes Registered image size names.
* @return array<int, string> Filtered size names.
*/
public function filter_intermediate_image_sizes( array $sizes ): array {
$mode = $this->effective_mode();
if ( self::MODE_ALL === $mode ) {
return $sizes;
}
if ( self::MODE_THUMBNAIL === $mode ) {
return in_array( 'thumbnail', $sizes, true ) ? array( 'thumbnail' ) : array();
}
return array();
}
/**
* Filter the big image size threshold.
*
* Returns 0 in 'thumbnail' and 'none' modes to disable the '-scaled'
* derivative that WordPress creates for images larger than the threshold.
*
* Note: WordPress fires this filter since 5.3. On older versions the
* callback is a no-op; the mode still applies via intermediate_image_sizes.
*
* @since 1.3.0
*
* @param int $threshold Threshold in pixels. Default 2560.
* @param array<int, int> $imagesize Indexed array of width and height.
* @param string $file Path to the uploaded file.
* @param int $attachment_id Attachment post ID.
* @return int Threshold in pixels, or 0 to disable.
*/
public function filter_big_image_threshold( int $threshold, array $imagesize = array(), string $file = '', int $attachment_id = 0 ): int {
if ( self::MODE_ALL === $this->effective_mode() ) {
return $threshold;
}
return 0;
}
/**
* Resolve the effective mode, defaulting to 'all' for any value that is
* not on the whitelist.
*
* @since 1.3.0
*
* @return string One of the MODE_* constants.
*/
private function effective_mode(): string {
$mode = $this->config->get_subsizes_mode();
return in_array( $mode, self::ALLOWED_MODES, true ) ? $mode : self::MODE_ALL;
}
}