Files
WPS3Media/classes/filters/as3cf-s3-to-local.php
Malin 3248cbb029 feat: add S3-compatible storage provider (MinIO, Ceph, R2, etc.)
Adds a new 'S3-Compatible Storage' provider that works with any
S3-API-compatible object storage service, including MinIO, Ceph,
Cloudflare R2, Backblaze B2, and others.

Changes:
- New provider class: classes/providers/storage/s3-compatible-provider.php
  - Provider key: s3compatible
  - Reads user-configured endpoint URL from settings
  - Uses path-style URL access (required by most S3-compatible services)
  - Supports credentials via AS3CF_S3COMPAT_ACCESS_KEY_ID /
    AS3CF_S3COMPAT_SECRET_ACCESS_KEY wp-config.php constants
  - Disables AWS-specific features (Block Public Access, Object Ownership)
- New provider SVG icons (s3compatible.svg, -link.svg, -round.svg)
- Registered provider in main plugin class with endpoint setting support
- Updated StorageProviderSubPage to show endpoint URL input for S3-compatible
- Built pro settings bundle with rollup (Svelte 4.2.19)
- Added package.json and updated rollup.config.mjs for pro-only builds
2026-03-03 12:30:18 +01:00

267 lines
6.7 KiB
PHP

<?php
use DeliciousBrains\WP_Offload_Media\Items\Item;
class AS3CF_S3_To_Local extends AS3CF_Filter {
/**
* @inheritDoc
*/
public function setup() {
parent::setup();
// EDD
add_filter( 'edd_metabox_save_edd_download_files', array( $this, 'filter_edd_download_files' ) );
// Customizer
add_filter( 'pre_set_theme_mod_background_image', array( $this, 'filter_customizer_image' ), 10, 2 );
add_filter( 'pre_set_theme_mod_header_image', array( $this, 'filter_customizer_image' ), 10, 2 );
add_filter( 'pre_set_theme_mod_header_image_data', array( $this, 'filter_header_image_data' ), 10, 2 );
add_filter( 'update_custom_css_data', array( $this, 'filter_update_custom_css_data' ), 10, 2 );
// Posts
add_filter( 'content_save_pre', array( $this, 'filter_post' ) );
add_filter( 'excerpt_save_pre', array( $this, 'filter_post' ) );
add_filter( 'as3cf_filter_post_s3_to_local', array( $this, 'filter_post' ) ); // Backwards compatibility
add_filter( 'as3cf_filter_post_provider_to_local', array( $this, 'filter_post' ) );
// Widgets
add_filter( 'widget_update_callback', array( $this, 'filter_widget_save' ) );
add_filter( 'pre_update_option_widget_block', array( $this, 'filter_widget_block_save' ) );
}
/**
* Filter update custom CSS data.
*
* @param array $data
* @param array $args
*
* @return array
*/
public function filter_update_custom_css_data( $data, $args ) {
$data['css'] = $this->filter_custom_css( $data['css'], $args['stylesheet'] );
return $data;
}
/**
* Filter widget on save.
*
* @param array $instance
*
* @return array
*/
public function filter_widget_save( $instance ) {
return $this->handle_widget( $instance );
}
/**
* Filter widget block on save.
*
* @param array $value The new, unserialized option value.
*
* @return array
*/
public function filter_widget_block_save( $value ) {
if ( empty( $value ) || ! is_array( $value ) ) {
return $value;
}
foreach ( $value as $idx => $section ) {
$value[ $idx ] = $this->handle_widget( $section );
}
return $value;
}
/**
* Should filter content.
*
* @return bool
*/
protected function should_filter_content() {
return true;
}
/**
* Does URL need replacing?
*
* @param string $url
*
* @return bool
*/
public function url_needs_replacing( $url ) {
if ( str_replace( $this->get_bare_upload_base_urls(), '', $url ) !== $url ) {
// Local URL, no replacement needed.
return false;
}
if ( str_replace( $this->get_remote_domains(), '', $url ) === $url ) {
// Not a known remote URL, no replacement needed.
return false;
}
// Remote URL, perform replacement
return true;
}
/**
* Get URL
*
* @param array $item_source
* @param null|string $object_key
*
* @return bool|string
*/
protected function get_url( $item_source, $object_key = null ) {
if ( Item::is_empty_item_source( $item_source ) ) {
return false;
}
/**
* Return the local URL for an item
*
* @param string|false $url Url for the item, false if no URL can be determined
* @param array $item_source Associative array describing the item, guaranteed keys:-
* id: source item's unique integer id
* source_type: source item's string type identifier
* @param string|null $object_key Object key (size) describing what sub file of an item to return url for
*/
return apply_filters( 'as3cf_get_local_url_for_item_source', false, $item_source, $object_key );
}
/**
* Get base URL.
*
* @param array $item_source
*
* @return string|false
*/
protected function get_base_url( $item_source ) {
if ( Item::is_empty_item_source( $item_source ) ) {
return false;
}
/**
* Return the provider URL for an item
*
* @param string|false $url Url for the item, false if no URL can be determined
* @param array $item_source Associative array describing the item, guaranteed keys:-
* id: source item's unique integer id
* source_type: source item's string type identifier
* @param string|null $object_key Object key (size) describing what sub file of an item to return url for
*/
return apply_filters( 'as3cf_get_provider_url_for_item_source', false, $item_source, null );
}
/**
* Get item source descriptor from URL.
*
* @param string $url
*
* @return bool|array
*/
public function get_item_source_from_url( $url ) {
// Result for sized URL already cached in request, return it.
if ( isset( $this->query_cache[ $url ] ) ) {
return $this->query_cache[ $url ];
}
$item_source = Item::get_item_source_by_remote_url( $url );
if ( ! Item::is_empty_item_source( $item_source ) ) {
$this->query_cache[ $url ] = $item_source;
return $item_source;
}
$full_url = AS3CF_Utils::remove_size_from_filename( $url );
// If we've already tried to find this URL above because it didn't have a size suffix, cache and return.
if ( $url === $full_url ) {
$this->query_cache[ $url ] = $item_source;
return $item_source;
}
// Result for URL already cached in request whether found or not, return it.
if ( isset( $this->query_cache[ $full_url ] ) ) {
return $this->query_cache[ $full_url ];
}
$item_source = Item::get_item_source_by_remote_url( $full_url );
$this->query_cache[ $full_url ] = ! Item::is_empty_item_source( $item_source ) ? $item_source : false;
return $this->query_cache[ $full_url ];
}
/**
* Get item source descriptors from URLs.
*
* @param array $urls
*
* @return array url => item source descriptor (or false)
*/
protected function get_item_sources_from_urls( $urls ) {
$results = array();
if ( empty( $urls ) ) {
return $results;
}
if ( ! is_array( $urls ) ) {
$urls = array( $urls );
}
foreach ( $urls as $url ) {
$results[ $url ] = $this->get_item_source_from_url( $url );
}
return $results;
}
/**
* Normalize find value.
*
* @param string $url
*
* @return string
*/
protected function normalize_find_value( $url ) {
return AS3CF_Utils::encode_filename_in_path( $url );
}
/**
* Normalize replace value.
*
* @param string $url
*
* @return string
*/
protected function normalize_replace_value( $url ) {
return AS3CF_Utils::decode_filename_in_path( $url );
}
/**
* Post process content.
*
* @param string $content
*
* @return string
*/
protected function post_process_content( $content ) {
$content = $this->remove_aws_query_strings( $content );
$content = AS3CF_Utils::maybe_fix_serialized_string( $content );
return $content;
}
/**
* Pre replace content.
*
* @param string $content
*
* @return string
*/
protected function pre_replace_content( $content ) {
return $content;
}
}