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
838 lines
24 KiB
PHP
838 lines
24 KiB
PHP
<?php
|
|
|
|
namespace DeliciousBrains\WP_Offload_Media\Items;
|
|
|
|
use Amazon_S3_And_CloudFront;
|
|
use AS3CF_Utils;
|
|
use WP_Error;
|
|
|
|
class Media_Library_Item extends Item {
|
|
/**
|
|
* Source type name
|
|
*
|
|
* @var string
|
|
*/
|
|
protected static $source_type_name = 'Media Library Item';
|
|
|
|
/**
|
|
* Internal source type identifier
|
|
*
|
|
* @var string
|
|
*/
|
|
protected static $source_type = 'media-library';
|
|
|
|
/**
|
|
* Table that corresponds to this item type
|
|
*
|
|
* @var string
|
|
*/
|
|
protected static $source_table = 'posts';
|
|
|
|
/**
|
|
* Foreign key (if any) in the $source_table
|
|
*
|
|
* @var string
|
|
*/
|
|
protected static $source_fk = 'id';
|
|
|
|
/**
|
|
* Item's summary type name.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected static $summary_type_name = 'Media Library';
|
|
|
|
/**
|
|
* Item's summary type.
|
|
*
|
|
* @var string
|
|
*/
|
|
protected static $summary_type = 'media-library';
|
|
|
|
/**
|
|
* Item constructor.
|
|
*
|
|
* @param string $provider Storage provider key name, e.g. "aws".
|
|
* @param string $region Region for item's bucket.
|
|
* @param string $bucket Bucket for item.
|
|
* @param string $path Key path for item (full sized if type has thumbnails etc).
|
|
* @param bool $is_private Is the object private in the bucket.
|
|
* @param int $source_id ID that source has.
|
|
* @param string $source_path Path that source uses, could be relative or absolute depending on source.
|
|
* @param string $original_filename An optional filename with no path that was previously used for the item.
|
|
* @param array $extra_info An optional associative array of extra data to be associated with the item.
|
|
* Recognised keys:
|
|
* 'objects' => array of ...
|
|
* -- 'thumbnail' => array of ...
|
|
* -- -- 'source_file' => 'image-150x150.png'
|
|
* -- -- 'is_private' => false
|
|
* 'private_prefix' => 'private/'
|
|
* For backwards compatibility, if a simple array is supplied it is treated as
|
|
* private thumbnail sizes that should be private objects in the bucket.
|
|
* @param int $id Optional Item record ID.
|
|
* @param int $originator Optional originator of record from ORIGINATORS const.
|
|
* @param bool $is_verified Optional flag as to whether Item's objects are known to exist.
|
|
* @param bool $use_object_versioning Optional flag as to whether path prefix should use Object Versioning if type allows it.
|
|
*/
|
|
public function __construct(
|
|
$provider,
|
|
$region,
|
|
$bucket,
|
|
$path,
|
|
$is_private,
|
|
$source_id,
|
|
$source_path,
|
|
$original_filename = null,
|
|
$extra_info = array(),
|
|
$id = null,
|
|
$originator = 0,
|
|
$is_verified = true,
|
|
$use_object_versioning = self::CAN_USE_OBJECT_VERSIONING
|
|
) {
|
|
// For Media Library items, the source path should be relative to the Media Library's uploads directory.
|
|
$uploads = wp_upload_dir();
|
|
|
|
if ( false === $uploads['error'] && 0 === strpos( $source_path, $uploads['basedir'] ) ) {
|
|
$source_path = AS3CF_Utils::unleadingslashit( substr( $source_path, strlen( $uploads['basedir'] ) ) );
|
|
}
|
|
|
|
$objects = array();
|
|
$private_prefix = null;
|
|
|
|
// Ensure re-hydration is clean.
|
|
if ( ! empty( $extra_info ) && is_array( $extra_info ) ) {
|
|
if ( isset( $extra_info['private_prefix'] ) ) {
|
|
$private_prefix = $extra_info['private_prefix'];
|
|
}
|
|
if ( isset( $extra_info['objects'] ) ) {
|
|
$objects = $extra_info['objects'];
|
|
}
|
|
}
|
|
|
|
$extra_info = array(
|
|
'objects' => $objects,
|
|
'private_prefix' => $private_prefix,
|
|
);
|
|
|
|
parent::__construct( $provider, $region, $bucket, $path, $is_private, $source_id, $source_path, $original_filename, $extra_info, $id, $originator, $is_verified, $use_object_versioning );
|
|
}
|
|
|
|
/**
|
|
* Synthesize a data struct to be used when passing information
|
|
* about the current item to filters that assume the item is a
|
|
* media library item.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function item_data_for_acl_filter() {
|
|
$item_data = parent::item_data_for_acl_filter();
|
|
$media_library_item_data = wp_get_attachment_metadata( $this->source_id(), true );
|
|
|
|
// Copy over specific elements only as i.e. 'size' may not be populated yet in $media_library_item_data
|
|
foreach ( array( 'file', 'original_image', 'image_meta' ) as $element ) {
|
|
if ( isset( $media_library_item_data[ $element ] ) ) {
|
|
$item_data[ $element ] = $media_library_item_data[ $element ];
|
|
}
|
|
}
|
|
|
|
return $item_data;
|
|
}
|
|
|
|
/**
|
|
* Create a new item from the source id.
|
|
*
|
|
* @param int $source_id
|
|
* @param array $options
|
|
*
|
|
* @return Item|WP_Error
|
|
*/
|
|
public static function create_from_source_id( $source_id, $options = array() ) {
|
|
if ( empty( $source_id ) ) {
|
|
return new WP_Error(
|
|
'exception',
|
|
__( 'Empty Attachment ID passed to ' . __FUNCTION__, 'amazon-s3-and-cloudfront' )
|
|
);
|
|
}
|
|
|
|
$default_options = array(
|
|
'originator' => Item::ORIGINATORS['standard'],
|
|
'is_verified' => true,
|
|
'use_object_versioning' => static::can_use_object_versioning(),
|
|
);
|
|
|
|
$options = array_merge( $default_options, $options );
|
|
|
|
if ( ! in_array( $options['originator'], self::ORIGINATORS ) ) {
|
|
return new WP_Error(
|
|
'exception',
|
|
__( 'Invalid Originator passed to ' . __FUNCTION__, 'amazon-s3-and-cloudfront' )
|
|
);
|
|
}
|
|
|
|
/*
|
|
* Derive local path.
|
|
*/
|
|
|
|
// Verify that get_attached_file will not blow up as it does not check the data it manipulates.
|
|
$attached_file_meta = get_post_meta( $source_id, '_wp_attached_file', true );
|
|
if ( ! is_string( $attached_file_meta ) ) {
|
|
return new WP_Error(
|
|
'exception',
|
|
sprintf( __( 'Media Library item with ID %d has damaged meta data', 'amazon-s3-and-cloudfront' ), $source_id )
|
|
);
|
|
}
|
|
unset( $attached_file_meta );
|
|
|
|
$source_path = get_attached_file( $source_id, true );
|
|
|
|
// Check for valid "full" file path otherwise we'll not be able to create offload path or download in the future.
|
|
if ( empty( $source_path ) ) {
|
|
return new WP_Error(
|
|
'exception',
|
|
sprintf( __( 'Media Library item with ID %d does not have a valid file path', 'amazon-s3-and-cloudfront' ), $source_id )
|
|
);
|
|
}
|
|
|
|
/** @var array|false|WP_Error $attachment_metadata */
|
|
$attachment_metadata = wp_get_attachment_metadata( $source_id, true );
|
|
if ( is_wp_error( $attachment_metadata ) ) {
|
|
return $attachment_metadata;
|
|
}
|
|
|
|
// Initialize extra info array with empty values
|
|
$extra_info = array(
|
|
'private_prefix' => null,
|
|
'objects' => array(),
|
|
);
|
|
|
|
// There may be an original image that can override the default original filename.
|
|
$original_filename = empty( $attachment_metadata['original_image'] ) ? null : $attachment_metadata['original_image'];
|
|
|
|
$file_paths = AS3CF_Utils::get_attachment_file_paths( $source_id, false, $attachment_metadata );
|
|
foreach ( $file_paths as $size => $size_file_path ) {
|
|
if ( $size === 'file' ) {
|
|
continue;
|
|
}
|
|
|
|
$new_object = array(
|
|
'source_file' => wp_basename( $size_file_path ),
|
|
'is_private' => false,
|
|
);
|
|
|
|
$extra_info['objects'][ $size ] = $new_object;
|
|
|
|
if ( empty( $original_filename ) && 'full-orig' === $size ) {
|
|
$original_filename = $new_object['source_file'];
|
|
}
|
|
}
|
|
|
|
return new self(
|
|
'',
|
|
'',
|
|
'',
|
|
'',
|
|
false,
|
|
$source_id,
|
|
$source_path,
|
|
$original_filename,
|
|
$extra_info,
|
|
null,
|
|
$options['originator'],
|
|
$options['is_verified'],
|
|
$options['use_object_versioning']
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get attachment local URL.
|
|
*
|
|
* This is partly a direct copy of wp_get_attachment_url() from /wp-includes/post.php
|
|
* as we filter the URL in AS3CF and can't remove this filter using the current implementation
|
|
* of globals for class instances.
|
|
*
|
|
* @param string|null $object_key
|
|
*
|
|
* @return string|false
|
|
*/
|
|
public function get_local_url( $object_key = null ) {
|
|
/** @var Amazon_S3_And_CloudFront $as3cf */
|
|
global $as3cf;
|
|
$url = '';
|
|
|
|
// Get attached file.
|
|
if ( $file = get_post_meta( $this->source_id(), '_wp_attached_file', true ) ) {
|
|
// Get upload directory.
|
|
if ( ( $uploads = wp_upload_dir() ) && false === $uploads['error'] ) {
|
|
// Check that the upload base exists in the file location.
|
|
if ( 0 === strpos( $file, $uploads['basedir'] ) ) {
|
|
// Replace file location with url location.
|
|
$url = str_replace( $uploads['basedir'], $uploads['baseurl'], $file );
|
|
} elseif ( false !== strpos( $file, 'wp-content/uploads' ) ) {
|
|
$url = $uploads['baseurl'] . substr( $file, strpos( $file, 'wp-content/uploads' ) + 18 );
|
|
} else {
|
|
// It's a newly-uploaded file, therefore $file is relative to the basedir.
|
|
$url = $uploads['baseurl'] . "/$file";
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( empty( $url ) ) {
|
|
return false;
|
|
}
|
|
|
|
$url = $as3cf->maybe_fix_local_subsite_url( $url );
|
|
|
|
if ( ! empty( $object_key ) ) {
|
|
$meta = get_post_meta( $this->source_id(), '_wp_attachment_metadata', true );
|
|
if ( empty( $meta['sizes'][ $object_key ]['file'] ) ) {
|
|
// No alternative sizes available, return
|
|
return $url;
|
|
}
|
|
|
|
$url = str_replace( wp_basename( $url ), $meta['sizes'][ $object_key ]['file'], $url );
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
|
|
/**
|
|
* Get the item based on source id.
|
|
*
|
|
* @param int $source_id
|
|
*
|
|
* @return bool|Media_Library_Item
|
|
*/
|
|
public static function get_by_source_id( $source_id ) {
|
|
$as3cf_item = parent::get_by_source_id( $source_id );
|
|
|
|
if ( ! $as3cf_item ) {
|
|
$provider_object = static::_legacy_get_attachment_provider_info( $source_id );
|
|
|
|
if ( is_array( $provider_object ) ) {
|
|
$as3cf_item = static::_legacy_provider_info_to_item( $source_id, $provider_object );
|
|
}
|
|
}
|
|
|
|
return $as3cf_item;
|
|
}
|
|
|
|
/**
|
|
* Full key (path) for given file that belongs to offloaded attachment.
|
|
*
|
|
* If no filename given, full sized path returned.
|
|
* Path is prepended with private prefix if size associated with filename is private,
|
|
* and a private prefix has been assigned to offload.
|
|
*
|
|
* @param string|null $filename
|
|
*
|
|
* @return string
|
|
*/
|
|
public function key( $filename = null ) {
|
|
// Public full path.
|
|
if ( empty( $filename ) && empty( $this->private_prefix() ) ) {
|
|
return parent::path();
|
|
}
|
|
|
|
if ( empty( $filename ) ) {
|
|
$filename = wp_basename( parent::path() );
|
|
}
|
|
|
|
if ( ! empty( $this->private_prefix() ) ) {
|
|
$size = $this->get_object_key_from_filename( $filename );
|
|
|
|
// Private path.
|
|
if ( $this->is_private( $size ) ) {
|
|
return $this->private_prefix() . $this->normalized_path_dir() . $filename;
|
|
}
|
|
}
|
|
|
|
// Public path.
|
|
return $this->normalized_path_dir() . $filename;
|
|
}
|
|
|
|
/**
|
|
* Get absolute source file paths for offloaded files.
|
|
*
|
|
* @return array Associative array of object_key => path
|
|
*/
|
|
public function full_source_paths() {
|
|
return array_intersect_key( AS3CF_Utils::get_attachment_file_paths( $this->source_id(), false ), $this->objects() );
|
|
}
|
|
|
|
/**
|
|
* Get size name from file name
|
|
*
|
|
* @param string $filename
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_object_key_from_filename( $filename ) {
|
|
return AS3CF_Utils::get_intermediate_size_from_filename( $this->source_id(), basename( $filename ) );
|
|
}
|
|
|
|
/**
|
|
* Get ACL for intermediate size.
|
|
*
|
|
* @param string $object_key Size name
|
|
* @param string|null $bucket Optional bucket that ACL is potentially to be used with.
|
|
*
|
|
* @return string|null
|
|
*/
|
|
public function get_acl_for_object_key( $object_key, $bucket = null ) {
|
|
/** @var Amazon_S3_And_CloudFront $as3cf */
|
|
global $as3cf;
|
|
|
|
return $as3cf->get_acl_for_intermediate_size( $this->source_id(), $object_key, $bucket, $this );
|
|
}
|
|
|
|
/**
|
|
* Get an array of un-managed source_ids in descending order.
|
|
*
|
|
* While source id isn't strictly unique, it is by source type, which is always used in queries based on called class.
|
|
*
|
|
* @param int $upper_bound Returned source_ids should be lower than this, use null/0 for no upper bound.
|
|
* @param int $limit Maximum number of source_ids to return. Required if not counting.
|
|
* @param bool $count Just return a count of matching source_ids? Negates $limit, default false.
|
|
*
|
|
* @return array|int
|
|
*/
|
|
public static function get_missing_source_ids( $upper_bound, $limit, $count = false ) {
|
|
global $wpdb;
|
|
|
|
$args = array( static::$source_type );
|
|
|
|
if ( $count ) {
|
|
$sql = 'SELECT COUNT(DISTINCT posts.ID)';
|
|
} else {
|
|
$sql = 'SELECT DISTINCT posts.ID';
|
|
}
|
|
|
|
$sql .= "
|
|
FROM {$wpdb->posts} AS posts
|
|
WHERE posts.post_type = 'attachment'
|
|
AND posts.ID NOT IN (
|
|
SELECT items.source_id
|
|
FROM " . static::items_table() . " AS items
|
|
WHERE items.source_type = %s
|
|
AND items.source_id = posts.ID
|
|
)
|
|
";
|
|
|
|
if ( ! empty( $upper_bound ) ) {
|
|
$sql .= ' AND posts.ID < %d';
|
|
$args[] = $upper_bound;
|
|
}
|
|
|
|
/**
|
|
* Allow users to exclude certain MIME types from attachments to upload.
|
|
*
|
|
* @param array
|
|
*/
|
|
$ignored_mime_types = apply_filters( 'as3cf_ignored_mime_types', array() );
|
|
if ( is_array( $ignored_mime_types ) && ! empty( $ignored_mime_types ) ) {
|
|
$ignored_mime_types = array_map( 'sanitize_text_field', $ignored_mime_types );
|
|
$sql .= " AND posts.post_mime_type NOT IN ('" . implode( "','", $ignored_mime_types ) . "')";
|
|
}
|
|
|
|
if ( ! $count ) {
|
|
$sql .= ' ORDER BY posts.ID DESC LIMIT %d';
|
|
$args[] = $limit;
|
|
}
|
|
|
|
$sql = $wpdb->prepare( $sql, $args );
|
|
|
|
if ( $count ) {
|
|
return (int) $wpdb->get_var( $sql );
|
|
} else {
|
|
return array_map( 'intval', $wpdb->get_col( $sql ) );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Finds Media Library items with same source_path and sets them as offloaded.
|
|
*/
|
|
public function offload_duplicate_items() {
|
|
global $wpdb;
|
|
|
|
$sql = $wpdb->prepare(
|
|
"
|
|
SELECT m.post_id
|
|
FROM " . $wpdb->postmeta . " AS m
|
|
LEFT JOIN " . $wpdb->posts . " AS p ON m.post_id = p.ID AND p.`post_type` = 'attachment'
|
|
WHERE m.meta_key = '_wp_attached_file'
|
|
AND m.meta_value = %s
|
|
AND m.post_id != %d
|
|
AND m.post_id NOT IN (
|
|
SELECT i.source_id
|
|
FROM " . static::items_table() . " AS i
|
|
WHERE i.source_type = %s
|
|
AND i.source_id = m.post_id
|
|
)
|
|
;
|
|
",
|
|
$this->source_path(),
|
|
$this->source_id(),
|
|
static::$source_type
|
|
);
|
|
|
|
$results = $wpdb->get_results( $sql );
|
|
|
|
// Nothing found, shortcut out.
|
|
if ( 0 === count( $results ) ) {
|
|
return;
|
|
}
|
|
|
|
foreach ( $results as $result ) {
|
|
$as3cf_item = new Media_Library_Item(
|
|
$this->provider(),
|
|
$this->region(),
|
|
$this->bucket(),
|
|
$this->path(),
|
|
$this->is_private(),
|
|
$result->post_id,
|
|
$this->source_path(),
|
|
wp_basename( $this->original_source_path() ),
|
|
$this->extra_info()
|
|
);
|
|
$as3cf_item->save();
|
|
$as3cf_item->duplicate_filesize_total( $this->source_id() );
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns a link to the items edit page in WordPress
|
|
*
|
|
* @param object $error
|
|
*
|
|
* @return object|null Object containing url and link text
|
|
*/
|
|
public static function admin_link( $error ) {
|
|
return (object) array(
|
|
'url' => get_edit_post_link( $error->source_id, '' ),
|
|
'text' => __( 'Edit', 'amazon-s3-and-cloudfront' ),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Return a year/month string for the item
|
|
*
|
|
* @return string
|
|
*/
|
|
protected function get_item_time() {
|
|
return $this->get_attachment_folder_year_month();
|
|
}
|
|
|
|
/**
|
|
* Get the year/month string for attachment's upload.
|
|
*
|
|
* Fall back to post date if attached, otherwise current date.
|
|
*
|
|
* @param array $data
|
|
*
|
|
* @return string
|
|
*/
|
|
private function get_attachment_folder_year_month( $data = array() ) {
|
|
if ( empty( $data ) ) {
|
|
$data = wp_get_attachment_metadata( $this->source_id(), true );
|
|
}
|
|
|
|
if ( isset( $data['file'] ) ) {
|
|
$time = $this->get_folder_time_from_url( $data['file'] );
|
|
}
|
|
|
|
if ( empty( $time ) && ( $local_url = wp_get_attachment_url( $this->source_id() ) ) ) {
|
|
$time = $this->get_folder_time_from_url( $local_url );
|
|
}
|
|
|
|
if ( empty( $time ) ) {
|
|
$time = date( 'Y/m' );
|
|
|
|
if ( ! ( $attach = get_post( $this->source_id() ) ) ) {
|
|
return $time;
|
|
}
|
|
|
|
if ( ! $attach->post_parent ) {
|
|
return $time;
|
|
}
|
|
|
|
if ( ! ( $post = get_post( $attach->post_parent ) ) ) {
|
|
return $time;
|
|
}
|
|
|
|
if ( substr( $post->post_date_gmt, 0, 4 ) > 0 ) {
|
|
return date( 'Y/m', strtotime( $post->post_date_gmt . ' +0000' ) );
|
|
}
|
|
}
|
|
|
|
return $time;
|
|
}
|
|
|
|
/**
|
|
* Get the upload folder time from given URL
|
|
*
|
|
* @param string $url
|
|
*
|
|
* @return null|string YYYY/MM format.
|
|
*/
|
|
private function get_folder_time_from_url( $url ) {
|
|
if ( ! is_string( $url ) ) {
|
|
return null;
|
|
}
|
|
|
|
preg_match( '@[0-9]{4}/[0-9]{2}/@', $url, $matches );
|
|
|
|
if ( isset( $matches[0] ) ) {
|
|
return untrailingslashit( $matches[0] );
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Returns filesize from metadata, if we have it, so that file or stream
|
|
* wrapper does not need to be hit.
|
|
*
|
|
* @return false|int
|
|
*/
|
|
public function get_filesize() {
|
|
// Prefer the canonical attachment filesize.
|
|
$metadata = get_post_meta( $this->source_id(), '_wp_attachment_metadata', true );
|
|
|
|
if ( ! empty( $metadata['filesize'] ) && is_int( $metadata['filesize'] ) ) {
|
|
return $metadata['filesize'];
|
|
}
|
|
|
|
// If offloaded and removed, we should have squirreled away the filesize.
|
|
$filesize = get_post_meta( $this->source_id(), 'as3cf_filesize_total', true );
|
|
|
|
if ( ! empty( $filesize ) && is_int( $filesize ) ) {
|
|
return $filesize;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Update filesize and as3cf_filesize_total metadata on the underlying media library item
|
|
* after removing the local file.
|
|
*
|
|
* @param int $original_size
|
|
* @param int $total_size
|
|
*/
|
|
public function update_filesize_after_remove_local( $original_size, $total_size ) {
|
|
update_post_meta( $this->source_id(), 'as3cf_filesize_total', $total_size );
|
|
|
|
if ( 0 < $original_size && ( $data = get_post_meta( $this->source_id(), '_wp_attachment_metadata', true ) ) ) {
|
|
if ( is_array( $data ) && empty( $data['filesize'] ) ) {
|
|
$data['filesize'] = $original_size;
|
|
|
|
// Update metadata with filesize
|
|
update_post_meta( $this->source_id(), '_wp_attachment_metadata', $data );
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Cleanup filesize and as3cf_filesize_total metadata on the underlying media library item
|
|
* after downloading a file back from the bucket
|
|
*/
|
|
public function update_filesize_after_download_local() {
|
|
$data = get_post_meta( $this->source_id(), '_wp_attachment_metadata', true );
|
|
|
|
/*
|
|
* Audio and video have a filesize added to metadata by default, but images and anything else don't.
|
|
* Note: Could have used `wp_generate_attachment_metadata` here to test whether default metadata has 'filesize',
|
|
* but it not only has side effects it also does a lot of work considering it's not a huge deal for this entry to hang around.
|
|
*/
|
|
if (
|
|
! empty( $data ) &&
|
|
(
|
|
empty( $data['mime_type'] ) ||
|
|
0 === strpos( $data['mime_type'], 'image/' ) ||
|
|
! ( 0 === strpos( $data['mime_type'], 'audio/' ) || 0 === strpos( $data['mime_type'], 'video/' ) )
|
|
)
|
|
) {
|
|
unset( $data['filesize'] );
|
|
update_post_meta( $this->source_id(), '_wp_attachment_metadata', $data );
|
|
}
|
|
|
|
delete_post_meta( $this->source_id(), 'as3cf_filesize_total' );
|
|
}
|
|
|
|
/**
|
|
* Duplicate 'as3cf_filesize_total' meta if it exists for an attachment.
|
|
*
|
|
* @param int $attachment_id
|
|
*/
|
|
public function duplicate_filesize_total( $attachment_id ) {
|
|
if ( ! ( $filesize = get_post_meta( $attachment_id, 'as3cf_filesize_total', true ) ) ) {
|
|
// No filesize to duplicate.
|
|
return;
|
|
}
|
|
|
|
update_post_meta( $this->source_id(), 'as3cf_filesize_total', $filesize );
|
|
}
|
|
|
|
/**
|
|
* If another item in current site shares full size *local* paths, only remove remote files not referenced by duplicates.
|
|
* We reference local paths as they should be reflected one way or another remotely, including backups.
|
|
*
|
|
* @param Item $as3cf_item
|
|
* @param array $paths
|
|
*/
|
|
public function remove_duplicate_paths( Item $as3cf_item, $paths ) {
|
|
$full_size_paths = AS3CF_Utils::fullsize_paths( $as3cf_item->full_source_paths() );
|
|
$as3cf_items_with_paths = static::get_by_source_path( $full_size_paths, array( $as3cf_item->source_id() ), false );
|
|
|
|
$duplicate_paths = array();
|
|
|
|
foreach ( $as3cf_items_with_paths as $as3cf_item_with_path ) {
|
|
/* @var Media_Library_Item $as3cf_item_with_path */
|
|
$duplicate_paths += array_values( AS3CF_Utils::get_attachment_file_paths( $as3cf_item_with_path->source_id(), false, false, true ) );
|
|
}
|
|
|
|
if ( ! empty( $duplicate_paths ) ) {
|
|
$paths = array_diff( $paths, $duplicate_paths );
|
|
}
|
|
|
|
return $paths;
|
|
}
|
|
|
|
/**
|
|
* Returns the transient key to be used for storing blog specific item counts.
|
|
*
|
|
* @param int $blog_id
|
|
*
|
|
* @return string
|
|
*/
|
|
public static function transient_key_for_item_counts( int $blog_id ): string {
|
|
return 'as3cf_' . absint( $blog_id ) . '_attachment_counts';
|
|
}
|
|
|
|
/**
|
|
* Count total, offloaded and not offloaded items on current site.
|
|
*
|
|
* @return array Keys:
|
|
* total: Total media count for site (current blog id)
|
|
* offloaded: Count of offloaded media for site (current blog id)
|
|
* not_offloaded: Difference between total and offloaded
|
|
*/
|
|
protected static function get_item_counts(): array {
|
|
global $wpdb;
|
|
|
|
$sql = "SELECT count(id) FROM {$wpdb->posts} WHERE post_type = 'attachment'";
|
|
$attachment_count = (int) $wpdb->get_var( $sql );
|
|
|
|
$sql = 'SELECT count(id) FROM ' . static::items_table() . ' WHERE source_type = %s';
|
|
$sql = $wpdb->prepare( $sql, static::$source_type );
|
|
$offloaded_count = (int) $wpdb->get_var( $sql );
|
|
|
|
return array(
|
|
'total' => $attachment_count,
|
|
'offloaded' => $offloaded_count,
|
|
'not_offloaded' => max( $attachment_count - $offloaded_count, 0 ),
|
|
);
|
|
}
|
|
|
|
/*
|
|
* >>> LEGACY ROUTINES BEGIN >>>
|
|
*/
|
|
|
|
/**
|
|
* Convert the provider info array for an attachment to item object.
|
|
*
|
|
* phpcs:disable PSR2.Methods.MethodDeclaration.Underscore
|
|
*
|
|
* @param int $source_id
|
|
* @param array $provider_info
|
|
*
|
|
* @return bool|Media_Library_Item
|
|
*/
|
|
private static function _legacy_provider_info_to_item( $source_id, $provider_info ) {
|
|
$attached_file = get_post_meta( $source_id, '_wp_attached_file', true );
|
|
|
|
if ( is_string( $attached_file ) && ! empty( $attached_file ) ) {
|
|
$private_sizes = array();
|
|
|
|
if ( ! empty( $provider_info['sizes'] ) && is_array( $provider_info['sizes'] ) ) {
|
|
$private_sizes = array_keys( $provider_info['sizes'] );
|
|
}
|
|
|
|
return new static(
|
|
$provider_info['provider'],
|
|
$provider_info['region'],
|
|
$provider_info['bucket'],
|
|
$provider_info['key'],
|
|
isset( $provider_info['acl'] ) && false !== strpos( $provider_info['acl'], 'private' ),
|
|
$source_id,
|
|
$attached_file,
|
|
wp_basename( $attached_file ),
|
|
$private_sizes
|
|
);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get attachment provider info
|
|
*
|
|
* @param int $post_id
|
|
*
|
|
* @return bool|array
|
|
*/
|
|
private static function _legacy_get_attachment_provider_info( $post_id ) {
|
|
$provider_object = get_post_meta( $post_id, 'amazonS3_info', true );
|
|
|
|
if ( ! empty( $provider_object ) && is_array( $provider_object ) && ! empty( $provider_object['bucket'] ) && ! empty( $provider_object['key'] ) ) {
|
|
global $as3cf;
|
|
|
|
$provider_object = array_merge( array(
|
|
'provider' => $as3cf::get_default_storage_provider(),
|
|
), $provider_object );
|
|
} else {
|
|
return false;
|
|
}
|
|
|
|
$provider_object['region'] = static::_legacy_get_provider_object_region( $provider_object );
|
|
|
|
if ( is_wp_error( $provider_object['region'] ) ) {
|
|
return false;
|
|
}
|
|
|
|
$provider_object = apply_filters( 'as3cf_get_attachment_s3_info', $provider_object, $post_id ); // Backwards compatibility
|
|
|
|
return apply_filters( 'as3cf_get_attachment_provider_info', $provider_object, $post_id );
|
|
}
|
|
|
|
/**
|
|
* Get the region of the bucket stored in the provider metadata.
|
|
*
|
|
* @param array $provider_object
|
|
*
|
|
* @return string|WP_Error - region name
|
|
*/
|
|
private static function _legacy_get_provider_object_region( $provider_object ) {
|
|
if ( ! isset( $provider_object['region'] ) ) {
|
|
/** @var Amazon_S3_And_CloudFront $as3cf */
|
|
global $as3cf;
|
|
|
|
// If region hasn't been stored in the provider metadata, retrieve using the bucket.
|
|
$region = $as3cf->get_bucket_region( $provider_object['bucket'] );
|
|
|
|
// Could just return $region here regardless, but this format is good for debug during legacy migration.
|
|
if ( is_wp_error( $region ) ) {
|
|
return $region;
|
|
}
|
|
|
|
$provider_object['region'] = $region;
|
|
}
|
|
|
|
return $provider_object['region'];
|
|
}
|
|
|
|
/*
|
|
* <<< LEGACY ROUTINES END <<<
|
|
*/
|
|
}
|