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
This commit is contained in:
95
classes/pro/background-processes/add-metadata-process.php
Normal file
95
classes/pro/background-processes/add-metadata-process.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Background_Processes;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
|
||||
use Exception;
|
||||
|
||||
class Add_Metadata_Process extends Uploader_Process {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'add_metadata';
|
||||
|
||||
/**
|
||||
* Increased batch limit as there is no network or file handling.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $limit = 2000;
|
||||
|
||||
/**
|
||||
* Increased chunk size as there is no network or file handling.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $chunk = 100;
|
||||
|
||||
/**
|
||||
* Create metadata as if item has been offloaded to provider, but don't actually do an offload.
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param int $source_id
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function handle_item( $source_type, $source_id, $blog_id ) {
|
||||
// Skip item if already on provider.
|
||||
if ( Media_Library_Item::get_by_source_id( $source_id ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$options = array(
|
||||
'originator' => Item::ORIGINATORS['metadata-tool'],
|
||||
'is_verified' => false,
|
||||
'use_object_versioning' => false,
|
||||
);
|
||||
$as3cf_item = Media_Library_Item::create_from_source_id( $source_id, $options );
|
||||
|
||||
// Build error message
|
||||
if ( is_wp_error( $as3cf_item ) ) {
|
||||
foreach ( $as3cf_item->get_error_messages() as $error_message ) {
|
||||
$error_msg = sprintf( __( 'Error adding metadata - %s', 'amazon-s3-and-cloudfront' ), $error_message );
|
||||
$this->record_error( $blog_id, $source_type, $source_id, $error_msg );
|
||||
}
|
||||
|
||||
return false;
|
||||
} else {
|
||||
$as3cf_item->save();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reached license limit notice message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_reached_license_limit_message() {
|
||||
$account_link = sprintf( '<a href="%s" target="_blank">%s</a>', $this->as3cf->get_my_account_url(), __( 'My Account', 'amazon-s3-and-cloudfront' ) );
|
||||
$notice_msg = __( "You've reached your license limit so we've had to stop adding metadata. To add metadata to the rest of your Media Library, please upgrade your license from %s and simply start the add metadata tool again. It will start from where it stopped.", 'amazon-s3-and-cloudfront' );
|
||||
|
||||
return sprintf( $notice_msg, $account_link );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete notice message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_complete_message() {
|
||||
return __( 'Finished adding metadata to Media Library.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has completed.
|
||||
*/
|
||||
protected function completed() {
|
||||
$this->as3cf->update_media_library_total();
|
||||
}
|
||||
}
|
||||
108
classes/pro/background-processes/analyze-and-repair-process.php
Normal file
108
classes/pro/background-processes/analyze-and-repair-process.php
Normal file
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Background_Processes;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
|
||||
abstract class Analyze_And_Repair_Process extends Background_Tool_Process {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'analyze_and_repair';
|
||||
|
||||
/**
|
||||
* Process items chunk.
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param array $source_ids
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function process_items_chunk( $source_type, $source_ids, $blog_id ) {
|
||||
$processed = $source_ids;
|
||||
|
||||
foreach ( $source_ids as $source_id ) {
|
||||
if ( $this->as3cf->is_attachment_served_by_provider( $source_id, true, true ) ) {
|
||||
$this->handle_item( $source_type, $source_id, $blog_id );
|
||||
}
|
||||
}
|
||||
|
||||
// Whether handled or not, we processed every item.
|
||||
return $processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Analyze and repair each item's offload metadata and log any errors.
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param int $source_id
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function handle_item( $source_type, $source_id, $blog_id ) {
|
||||
$as3cf_item = Media_Library_Item::get_by_source_id( $source_id );
|
||||
|
||||
if ( empty( $as3cf_item ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$result = $this->analyze_and_repair( $as3cf_item );
|
||||
|
||||
// Build generic error message.
|
||||
if ( is_wp_error( $result ) ) {
|
||||
foreach ( $result->get_error_messages() as $error_message ) {
|
||||
$error_msg = sprintf( __( 'Error - %s', 'amazon-s3-and-cloudfront' ), $error_message );
|
||||
$this->record_error( $blog_id, $source_type, $source_id, $error_msg );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs required analysis and repairs for given offloaded item.
|
||||
*
|
||||
* @param Media_Library_Item $as3cf_item
|
||||
*
|
||||
* @return bool|WP_Error Returns false if no action required, true if repaired, or WP_Error if could not be processed or repaired.
|
||||
*/
|
||||
abstract protected function analyze_and_repair( Media_Library_Item $as3cf_item );
|
||||
|
||||
/**
|
||||
* Called when background process has been cancelled.
|
||||
*/
|
||||
protected function cancelled() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been paused.
|
||||
*/
|
||||
protected function paused() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been resumed.
|
||||
*/
|
||||
protected function resumed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has completed.
|
||||
*/
|
||||
protected function completed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Analyze_And_Repair;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Analyze_And_Repair_Process;
|
||||
use WP_Error;
|
||||
|
||||
class Reverse_Add_Metadata_Process extends Analyze_And_Repair_Process {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'reverse_add_metadata';
|
||||
|
||||
/**
|
||||
* Get blog items to process.
|
||||
*
|
||||
* @param string $source_type Item source type
|
||||
* @param int $last_source_id The ID of the last item previously processed
|
||||
* @param int $limit Maximum number of item IDs to return
|
||||
* @param bool $count Just return the count, negates $limit, default false
|
||||
*
|
||||
* @return array|int
|
||||
*/
|
||||
protected function get_blog_items( $source_type, $last_source_id, $limit, $count = false ) {
|
||||
return Media_Library_Item::get_source_ids( $last_source_id, $limit, $count, Item::ORIGINATORS['metadata-tool'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs required analysis and repairs for given offloaded item.
|
||||
*
|
||||
* @param Media_Library_Item $as3cf_item
|
||||
*
|
||||
* @return bool|WP_Error Returns false if no action required, true if repaired, or WP_Error if could not be processed or repaired.
|
||||
*/
|
||||
protected function analyze_and_repair( Media_Library_Item $as3cf_item ) {
|
||||
return $as3cf_item->delete();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete notice message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_complete_message() {
|
||||
return __( 'Finished removing items previously created with the Add Metadata tool.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has completed.
|
||||
*/
|
||||
protected function completed() {
|
||||
delete_site_option( $this->prefix . '_add_metadata_last_started' );
|
||||
$this->as3cf->update_media_library_total();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Analyze_And_Repair;
|
||||
|
||||
use AS3CF_Utils;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Analyze_And_Repair_Process;
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
|
||||
class Verify_Add_Metadata_Process extends Analyze_And_Repair_Process {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'verify_add_metadata';
|
||||
|
||||
/**
|
||||
* Get blog items to process.
|
||||
*
|
||||
* @param string $source_type Item source type
|
||||
* @param int $last_source_id The ID of the last item previously processed
|
||||
* @param int $limit Maximum number of item IDs to return
|
||||
* @param bool $count Just return the count, negates $limit, default false
|
||||
*
|
||||
* @return array|int
|
||||
*/
|
||||
protected function get_blog_items( $source_type, $last_source_id, $limit, $count = false ) {
|
||||
return Media_Library_Item::get_source_ids( $last_source_id, $limit, $count, Item::ORIGINATORS['metadata-tool'], false );
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs required analysis and repairs for given offloaded item.
|
||||
*
|
||||
* @param Media_Library_Item $as3cf_item
|
||||
*
|
||||
* @return bool|WP_Error Returns false if no action required, true if repaired, or WP_Error if could not be processed or repaired.
|
||||
*/
|
||||
protected function analyze_and_repair( Media_Library_Item $as3cf_item ) {
|
||||
$bucket = $as3cf_item->bucket();
|
||||
|
||||
if ( empty( $bucket ) ) {
|
||||
return new WP_Error( 'exception', 'Could not get bucket for item.' );
|
||||
}
|
||||
|
||||
$region = $as3cf_item->region();
|
||||
|
||||
if ( is_wp_error( $region ) ) {
|
||||
return new WP_Error( 'exception', 'Could not get region for bucket: ' . $region->get_error_message() );
|
||||
} elseif ( ! is_string( $region ) ) {
|
||||
return new WP_Error( 'exception', "Could not get region for item's bucket." );
|
||||
}
|
||||
|
||||
try {
|
||||
$provider_client = $this->as3cf->get_provider_client( $region );
|
||||
} catch ( Exception $e ) {
|
||||
return new WP_Error( 'exception', 'Could not get provider client: ' . $e->getMessage() );
|
||||
}
|
||||
|
||||
$paths = AS3CF_Utils::get_attachment_file_paths( $as3cf_item->source_id(), false );
|
||||
|
||||
if ( empty( $paths ) ) {
|
||||
return new WP_Error( 'exception', 'Could not get paths for Media Library item with ID: ' . $as3cf_item->source_id() );
|
||||
}
|
||||
|
||||
$fullsize_paths = AS3CF_Utils::fullsize_paths( $paths );
|
||||
|
||||
if ( empty( $fullsize_paths ) ) {
|
||||
return new WP_Error( 'exception', 'Could not get full size paths for Media Library item with ID: ' . $as3cf_item->source_id() );
|
||||
}
|
||||
|
||||
$fullsize_exists = false;
|
||||
$fullsize_missing = false;
|
||||
|
||||
foreach ( $fullsize_paths as $path ) {
|
||||
$key = $as3cf_item->key( wp_basename( $path ) );
|
||||
|
||||
if ( $provider_client->does_object_exist( $bucket, $key ) ) {
|
||||
$fullsize_exists = true;
|
||||
} else {
|
||||
$fullsize_missing = true;
|
||||
}
|
||||
}
|
||||
|
||||
// A full sized file has not been found, remove metadata.
|
||||
if ( ! $fullsize_exists ) {
|
||||
$as3cf_item->delete();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// At least one full sized file has been found, set as verified.
|
||||
$as3cf_item->set_is_verified( true );
|
||||
$as3cf_item->save();
|
||||
|
||||
// Need to log that sizes need regeneration?
|
||||
// NOTE: As we currently do not have a means of setting individual thumbnails sizes as verified, we can shortcut out here.
|
||||
// NOTE: In the future we should record whether each size is verified and/or remove details record.
|
||||
if ( $fullsize_missing ) {
|
||||
return new WP_Error( 'exception', 'Thumbnails need regenerating for Media Library item with ID: ' . $as3cf_item->source_id() );
|
||||
}
|
||||
|
||||
// If item has sizes, check them too.
|
||||
$size_paths = array_diff( $paths, $fullsize_paths );
|
||||
|
||||
if ( empty( $size_paths ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach ( $size_paths as $path ) {
|
||||
$key = $as3cf_item->key( wp_basename( $path ) );
|
||||
|
||||
if ( ! $provider_client->does_object_exist( $bucket, $key ) ) {
|
||||
return new WP_Error( 'exception', 'Thumbnails need regenerating for Media Library item with ID: ' . $as3cf_item->source_id() );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete notice message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_complete_message() {
|
||||
return __( 'Finished checking or removing items previously created with the Add Metadata tool.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has completed.
|
||||
*/
|
||||
protected function completed() {
|
||||
$this->as3cf->update_media_library_total();
|
||||
}
|
||||
}
|
||||
387
classes/pro/background-processes/background-tool-process.php
Normal file
387
classes/pro/background-processes/background-tool-process.php
Normal file
@@ -0,0 +1,387 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Background_Processes;
|
||||
|
||||
use AS3CF_Background_Process;
|
||||
use AS3CF_Error;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Tool;
|
||||
|
||||
abstract class Background_Tool_Process extends AS3CF_Background_Process {
|
||||
|
||||
/**
|
||||
* @var Tool
|
||||
*/
|
||||
protected $tool;
|
||||
|
||||
/**
|
||||
* Default batch limit.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $limit = 100;
|
||||
|
||||
/**
|
||||
* Default chunk size.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $chunk = 10;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $errors = array();
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $reported_errors_limit = 100;
|
||||
|
||||
/**
|
||||
* Initiate new background tool process.
|
||||
*
|
||||
* @param object $as3cf Instance of calling class
|
||||
* @param Tool $tool
|
||||
*/
|
||||
public function __construct( $as3cf, $tool ) {
|
||||
parent::__construct( $as3cf );
|
||||
|
||||
$this->tool = $tool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Task
|
||||
*
|
||||
* Override this method to perform any actions required on each
|
||||
* queue item. Return the modified item for further processing
|
||||
* in the next pass through. Or, return false to remove the
|
||||
* item from the queue.
|
||||
*
|
||||
* @param mixed $item Queue item to iterate over
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
protected function task( $item ) {
|
||||
if ( ! $item['blogs_processed'] ) {
|
||||
// Calculate how many items each blog has,
|
||||
// and return immediately to allow monitoring
|
||||
// processes see initial state.
|
||||
$item = $this->calculate_blog_items( $item );
|
||||
|
||||
if ( $this->all_blog_items_processed( $item ) ) {
|
||||
// Nothing to do, remove from queue.
|
||||
return false;
|
||||
} else {
|
||||
return $item;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $this->all_blog_items_processed( $item ) ) {
|
||||
// Batch complete, remove from queue
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->process_blogs( $item );
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the number of items across all blogs.
|
||||
*
|
||||
* @param array $item
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function calculate_blog_items( $item ) {
|
||||
foreach ( $item['blogs'] as $blog_id => $blog ) {
|
||||
if ( $this->time_exceeded() || $this->memory_exceeded() ) {
|
||||
// Batch limits reached
|
||||
return $item;
|
||||
}
|
||||
|
||||
if ( ! is_null( $blog['total_items'] ) ) {
|
||||
// Blog already processed, move on
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->as3cf->switch_to_blog( $blog_id );
|
||||
|
||||
foreach ( $blog['processed'] as $source_type => $processed ) {
|
||||
if ( isset( $blog['last_source_id'][ $source_type ] ) && is_numeric( $blog['last_source_id'][ $source_type ] ) ) {
|
||||
$last_source_id = $blog['last_source_id'][ $source_type ];
|
||||
} else {
|
||||
$last_source_id = null;
|
||||
}
|
||||
|
||||
$total = $this->get_blog_items( $source_type, $last_source_id, null, true );
|
||||
|
||||
if ( ! empty( $total ) ) {
|
||||
$item['blogs'][ $blog_id ]['total_items'] += $total;
|
||||
$item['blogs'][ $blog_id ]['last_source_id'][ $source_type ] = $this->get_blog_last_source_id( $source_type ) + 1;
|
||||
$item['total_items'] += $total;
|
||||
} else {
|
||||
$item['blogs'][ $blog_id ]['processed'][ $source_type ] = true;
|
||||
$item['blogs'][ $blog_id ]['total_items'] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
$this->as3cf->restore_current_blog();
|
||||
}
|
||||
|
||||
$item['blogs_processed'] = true;
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loop over each blog and process items.
|
||||
*
|
||||
* @param array $item
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function process_blogs( $item ) {
|
||||
$this->errors = $this->tool->get_errors();
|
||||
|
||||
foreach ( $item['blogs'] as $blog_id => $blog ) {
|
||||
if ( $this->time_exceeded() || $this->memory_exceeded() ) {
|
||||
// Batch limits reached
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $this->all_source_types_processed( $blog ) ) {
|
||||
// Blog processed, move onto the next
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->as3cf->switch_to_blog( $blog_id );
|
||||
$limit = apply_filters( "as3cf_tool_{$this->action}_batch_size", $this->limit );
|
||||
|
||||
foreach ( $blog['last_source_id'] as $source_type => $last_source_id ) {
|
||||
$items = $this->get_blog_items( $source_type, $last_source_id, $limit );
|
||||
$item = $this->process_blog_items( $item, $blog_id, $source_type, $items );
|
||||
}
|
||||
|
||||
// If we've just finished processing a subsite, force update its totals.
|
||||
if ( is_multisite() && $this->all_source_types_processed( $item['blogs'][ $blog_id ] ) ) {
|
||||
$this->as3cf->media_counts( true, true, $blog_id );
|
||||
}
|
||||
|
||||
$this->as3cf->restore_current_blog();
|
||||
}
|
||||
|
||||
if ( count( $this->errors ) ) {
|
||||
$this->tool->update_errors( $this->errors );
|
||||
$this->tool->update_error_notice( $this->errors );
|
||||
$this->tool->undismiss_error_notice();
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process blog items.
|
||||
*
|
||||
* @param array $item
|
||||
* @param int $blog_id
|
||||
* @param string $source_type
|
||||
* @param array $items
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function process_blog_items( $item, $blog_id, $source_type, $items ) {
|
||||
$chunks = array_chunk( $items, $this->chunk );
|
||||
|
||||
foreach ( $chunks as $chunk ) {
|
||||
$processed = $this->process_items_chunk( $source_type, $chunk, $blog_id );
|
||||
|
||||
if ( ! empty( $processed ) ) {
|
||||
$item['processed_items'] += count( $processed );
|
||||
$item['blogs'][ $blog_id ]['last_source_id'][ $source_type ] = end( $processed );
|
||||
}
|
||||
|
||||
if ( $this->time_exceeded() || $this->memory_exceeded() || count( $chunk ) > count( $processed ) ) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $items ) ) {
|
||||
$item['blogs'][ $blog_id ]['processed'][ $source_type ] = true;
|
||||
}
|
||||
|
||||
return $item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get blog items to process.
|
||||
*
|
||||
* @param string $source_type Item source type
|
||||
* @param int $last_source_id The ID of the last item previously processed
|
||||
* @param int $limit Maximum number of item IDs to return
|
||||
* @param bool $count Just return the count, negates $limit, default false
|
||||
*
|
||||
* @return array|int
|
||||
*/
|
||||
protected function get_blog_items( $source_type, $last_source_id, $limit, $count = false ) {
|
||||
/** @var Item $class */
|
||||
$class = $this->as3cf->get_source_type_class( $source_type );
|
||||
|
||||
return $class::get_source_ids( $last_source_id, $limit, $count );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get blog last item ID.
|
||||
*
|
||||
* @param string $source_type Item source type
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function get_blog_last_source_id( $source_type ) {
|
||||
$items = $this->get_blog_items( $source_type, null, 1 );
|
||||
|
||||
return empty( $items ) ? 0 : reset( $items );
|
||||
}
|
||||
|
||||
/**
|
||||
* Have all blog items been processed?
|
||||
*
|
||||
* @param array $item
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function all_blog_items_processed( $item ) {
|
||||
foreach ( $item['blogs'] as $blog ) {
|
||||
if ( ! $this->all_source_types_processed( $blog ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Have all item types in blog been processed?
|
||||
*
|
||||
* @param array $blog
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function all_source_types_processed( $blog ) {
|
||||
foreach ( $blog['processed'] as $processed ) {
|
||||
if ( ! $processed ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Record error.
|
||||
*
|
||||
* @param int $blog_id
|
||||
* @param string $source_type
|
||||
* @param int $source_id
|
||||
* @param string $message
|
||||
*/
|
||||
protected function record_error( $blog_id, $source_type, $source_id, $message ) {
|
||||
AS3CF_Error::log( $message );
|
||||
|
||||
// Existing entry for item to append message to?
|
||||
foreach ( $this->errors as $error ) {
|
||||
if ( $error->blog_id === $blog_id && $error->source_type === $source_type && $error->source_id === $source_id ) {
|
||||
$error->messages[] = $message;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Restrict to $reported_errors_limit entries in the UI.
|
||||
if ( $this->count_errors() >= $this->reported_errors_limit ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->errors[] = (object) array(
|
||||
'blog_id' => $blog_id,
|
||||
'source_type' => $source_type,
|
||||
'source_id' => $source_id,
|
||||
'messages' => array( $message ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* How many items have had errors recorded by this process?
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function count_errors() {
|
||||
return is_array( $this->errors ) ? count( $this->errors ) : 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete
|
||||
*
|
||||
* Override if applicable, but ensure that the below actions are
|
||||
* performed, or, call parent::complete().
|
||||
*/
|
||||
protected function complete() {
|
||||
parent::complete();
|
||||
|
||||
$notice_id = $this->tool->get_tool_key() . '_completed';
|
||||
|
||||
$this->as3cf->notices->undismiss_notice_for_all( $notice_id );
|
||||
$this->as3cf->notices->remove_notice_by_id( $notice_id );
|
||||
|
||||
if ( $this->tool->get_errors() ) {
|
||||
$message = $this->get_complete_with_errors_message();
|
||||
$type = 'notice-warning';
|
||||
} else {
|
||||
$message = $this->get_complete_message();
|
||||
$type = 'updated';
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'custom_id' => $notice_id,
|
||||
'type' => $type,
|
||||
'flash' => false,
|
||||
'only_show_to_user' => false,
|
||||
);
|
||||
|
||||
$this->as3cf->notices->add_notice( $message, $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a note about errors to completion message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_complete_with_errors_message() {
|
||||
$msg = $this->get_complete_message() . ' ';
|
||||
$msg .= sprintf(
|
||||
'<a href="%1$s">',
|
||||
$this->as3cf->get_plugin_page_url( array( 'hash' => '/tools/' ) )
|
||||
);
|
||||
$msg .= __( 'Some errors were recorded.', 'amazon-s3-and-cloudfront' );
|
||||
$msg .= '</a>';
|
||||
|
||||
return $msg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process items chunk.
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param array $source_ids
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
abstract protected function process_items_chunk( $source_type, $source_ids, $blog_id );
|
||||
|
||||
/**
|
||||
* Get complete notice message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function get_complete_message();
|
||||
}
|
||||
235
classes/pro/background-processes/copy-buckets-process.php
Normal file
235
classes/pro/background-processes/copy-buckets-process.php
Normal file
@@ -0,0 +1,235 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Background_Processes;
|
||||
|
||||
use AS3CF_Error;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Providers\Storage\Storage_Provider;
|
||||
use Exception;
|
||||
|
||||
class Copy_Buckets_Process extends Background_Tool_Process {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'copy_buckets';
|
||||
|
||||
/**
|
||||
* Process items chunk.
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param array $source_ids
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function process_items_chunk( $source_type, $source_ids, $blog_id ) {
|
||||
$bucket = $this->as3cf->get_setting( 'bucket' );
|
||||
$region = $this->as3cf->get_setting( 'region' );
|
||||
$class = $this->as3cf->get_source_type_class( $source_type );
|
||||
|
||||
$items_to_copy = array();
|
||||
|
||||
foreach ( $source_ids as $source_id ) {
|
||||
/** @var Item $class */
|
||||
$as3cf_item = $class::get_by_source_id( $source_id );
|
||||
|
||||
if ( false === $as3cf_item || is_wp_error( $as3cf_item ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $as3cf_item->bucket() === $bucket ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$items_to_copy[] = $source_id;
|
||||
}
|
||||
|
||||
$this->copy_items( $items_to_copy, $blog_id, $bucket, $region, $source_type );
|
||||
|
||||
// Whether copied or not, we processed every item.
|
||||
return $source_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy items to new bucket.
|
||||
*
|
||||
* @param array $items
|
||||
* @param int $blog_id
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @param string $source_type
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function copy_items( $items, $blog_id, $bucket, $region, $source_type ) {
|
||||
if ( empty( $items ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$keys = $this->as3cf->get_provider_keys( $items, $source_type );
|
||||
|
||||
if ( empty( $keys ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$items_to_copy = array();
|
||||
$skipped = array();
|
||||
|
||||
/** @var Item $class */
|
||||
$class = $this->as3cf->get_source_type_class( $source_type );
|
||||
|
||||
foreach ( $keys as $source_id => $source_keys ) {
|
||||
/** @var Item $as3cf_item */
|
||||
$as3cf_item = $class::get_by_source_id( $source_id );
|
||||
|
||||
if ( ! $as3cf_item->served_by_provider( true ) ) {
|
||||
$name = $this->as3cf->get_source_type_name( $source_type );
|
||||
$skipped[] = array(
|
||||
'Key' => $source_keys[0],
|
||||
'Message' => sprintf( __( '%s item with ID %s is offloaded to a different provider than currently configured', 'amazon-s3-and-cloudfront' ), $name, $source_id ),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ( $source_keys as $key ) {
|
||||
|
||||
$args = array(
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $key,
|
||||
'CopySource' => urlencode( "{$as3cf_item->bucket()}/{$key}" ),
|
||||
);
|
||||
|
||||
$size = $as3cf_item->get_object_key_from_filename( $key );
|
||||
$acl = $as3cf_item->get_acl_for_object_key( $size, $bucket );
|
||||
|
||||
// Only set ACL if actually required, some storage provider and bucket settings disable changing ACL.
|
||||
if ( ! empty( $acl ) ) {
|
||||
$args['ACL'] = $acl;
|
||||
}
|
||||
|
||||
$args = Storage_Provider::filter_object_meta( $args, $as3cf_item, $size, true );
|
||||
|
||||
// Protect against filter use and only set ACL if actually required, some storage provider and bucket settings disable changing ACL.
|
||||
if ( isset( $args['ACL'] ) && empty( $acl ) ) {
|
||||
unset( $args['ACL'] );
|
||||
}
|
||||
|
||||
$items_to_copy[] = $args;
|
||||
}
|
||||
}
|
||||
|
||||
$failures = array();
|
||||
|
||||
if ( ! empty( $items_to_copy ) ) {
|
||||
$client = $this->as3cf->get_provider_client( $region, true );
|
||||
try {
|
||||
$failures = $client->copy_objects( $items_to_copy );
|
||||
} catch ( Exception $e ) {
|
||||
AS3CF_Error::log( $e->getMessage() );
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$failures = $failures + $skipped;
|
||||
|
||||
if ( ! empty( $failures ) ) {
|
||||
$keys = $this->handle_failed_keys( $keys, $failures, $blog_id, $source_type );
|
||||
}
|
||||
|
||||
$this->update_item_provider_info( $keys, $bucket, $region, $source_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle failed keys.
|
||||
*
|
||||
* @param array $keys
|
||||
* @param array $failures
|
||||
* @param int $blog_id
|
||||
* @param string $source_type
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function handle_failed_keys( $keys, $failures, $blog_id, $source_type ) {
|
||||
foreach ( $failures as $failure ) {
|
||||
foreach ( $keys as $source_id => $source_keys ) {
|
||||
if ( false !== array_search( $failure['Key'], $source_keys ) ) {
|
||||
$error_msg = sprintf( __( 'Error copying %s between buckets: %s', 'amazon-s3-and-cloudfront' ), $failure['Key'], $failure['Message'] );
|
||||
|
||||
$this->record_error( $blog_id, $source_type, $source_id, $error_msg );
|
||||
|
||||
unset( $keys[ $source_id ] );
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update item provider info.
|
||||
*
|
||||
* @param array $keys
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
* @param string $source_type
|
||||
*/
|
||||
protected function update_item_provider_info( $keys, $bucket, $region, $source_type ) {
|
||||
if ( empty( $keys ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Item $class */
|
||||
$class = $this->as3cf->get_source_type_class( $source_type );
|
||||
|
||||
foreach ( $keys as $source_id => $source_keys ) {
|
||||
$as3cf_item = $class::get_by_source_id( $source_id );
|
||||
|
||||
$as3cf_item->set_region( $region );
|
||||
$as3cf_item->set_bucket( $bucket );
|
||||
$as3cf_item->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete notice message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_complete_message() {
|
||||
return __( 'Finished copying media files to new bucket.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been cancelled.
|
||||
*/
|
||||
protected function cancelled() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been paused.
|
||||
*/
|
||||
protected function paused() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been resumed.
|
||||
*/
|
||||
protected function resumed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has completed.
|
||||
*/
|
||||
protected function completed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Background_Processes;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Items\Remove_Provider_Handler;
|
||||
use Exception;
|
||||
|
||||
class Download_And_Remover_Process extends Downloader_Process {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'download_and_remover';
|
||||
|
||||
/**
|
||||
* Download and remove the item from bucket.
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param int $source_id
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function handle_item( $source_type, $source_id, $blog_id ) {
|
||||
if ( parent::handle_item( $source_type, $source_id, $blog_id ) ) {
|
||||
/** @var Item $class */
|
||||
$class = $this->as3cf->get_source_type_class( $source_type );
|
||||
$as3cf_item = $class::get_by_source_id( $source_id );
|
||||
|
||||
/** @var Remove_Provider_Handler $remove_handler */
|
||||
$remove_handler = $this->as3cf->get_item_handler( Remove_Provider_Handler::get_item_handler_key_name() );
|
||||
|
||||
// As we've already confirmed that local files exist,
|
||||
// and not had to record any errors for display,
|
||||
// we can skip confirming that files exist on local,
|
||||
// or that the remove from provider succeeded.
|
||||
$result = $remove_handler->handle( $as3cf_item, array( 'verify_exists_on_local' => false ) );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
foreach ( $result->get_error_messages() as $error_message ) {
|
||||
$error_msg = sprintf( __( 'Error removing from bucket - %s', 'amazon-s3-and-cloudfront' ), $error_message );
|
||||
$this->record_error( $blog_id, $source_type, $source_id, $error_msg );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$as3cf_item->delete();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been cancelled.
|
||||
*/
|
||||
protected function cancelled() {
|
||||
$this->as3cf->update_media_library_total();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been paused.
|
||||
*/
|
||||
protected function paused() {
|
||||
$this->as3cf->update_media_library_total();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been resumed.
|
||||
*/
|
||||
protected function resumed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has completed.
|
||||
*/
|
||||
protected function completed() {
|
||||
$this->as3cf->update_media_library_total();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete notice message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_complete_message() {
|
||||
return __( 'Finished removing media files from bucket.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
}
|
||||
109
classes/pro/background-processes/downloader-process.php
Normal file
109
classes/pro/background-processes/downloader-process.php
Normal file
@@ -0,0 +1,109 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Background_Processes;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Download_Handler;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use Exception;
|
||||
|
||||
class Downloader_Process extends Background_Tool_Process {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'downloader';
|
||||
|
||||
/**
|
||||
* Process items chunk.
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param array $source_ids
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function process_items_chunk( $source_type, $source_ids, $blog_id ) {
|
||||
$processed = $source_ids;
|
||||
|
||||
foreach ( $source_ids as $source_id ) {
|
||||
$this->handle_item( $source_type, $source_id, $blog_id );
|
||||
}
|
||||
|
||||
// Whether downloaded to local or not, we processed every item.
|
||||
return $processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download the item from bucket.
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param int $source_id
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function handle_item( $source_type, $source_id, $blog_id ) {
|
||||
/** @var Item $class */
|
||||
$class = $this->as3cf->get_source_type_class( $source_type );
|
||||
$as3cf_item = $class::get_by_source_id( $source_id );
|
||||
|
||||
if ( ! $as3cf_item ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @var Download_Handler $download_handler */
|
||||
$download_handler = $this->as3cf->get_item_handler( Download_Handler::get_item_handler_key_name() );
|
||||
$result = $download_handler->handle( $as3cf_item );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
foreach ( $result->get_error_messages() as $error_message ) {
|
||||
$error_msg = sprintf( __( 'Error downloading to server - %s', 'amazon-s3-and-cloudfront' ), $error_message );
|
||||
$this->record_error( $blog_id, $source_type, $source_id, $error_msg );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete notice message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_complete_message() {
|
||||
return __( 'Finished downloading media files to local server.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been cancelled.
|
||||
*/
|
||||
protected function cancelled() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been paused.
|
||||
*/
|
||||
protected function paused() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been resumed.
|
||||
*/
|
||||
protected function resumed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has completed.
|
||||
*/
|
||||
protected function completed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Background_Processes;
|
||||
|
||||
use AS3CF_Error;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
|
||||
use Exception;
|
||||
|
||||
class Elementor_Analyze_And_Repair_Process extends Background_Tool_Process {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'elementor_analyze_and_repair';
|
||||
|
||||
/**
|
||||
* Process chunk of posts
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param array $source_ids
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function process_items_chunk( $source_type, $source_ids, $blog_id ) {
|
||||
foreach ( $source_ids as $source_id ) {
|
||||
$this->handle_item( $source_type, $source_id, $blog_id );
|
||||
}
|
||||
|
||||
// We processed every item.
|
||||
return $source_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process individual posts/pages with Elementor data and update any remote
|
||||
* URLs to the corresponding local version.
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param int $post_id
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function handle_item( $source_type, $post_id, $blog_id ) {
|
||||
$original_json = get_post_meta( $post_id, '_elementor_data', true );
|
||||
$skip_values = array( 'true', 'false', 'null' );
|
||||
|
||||
if ( empty( $original_json ) || ( is_string( $original_json ) && in_array( trim( $original_json ), $skip_values, true ) ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// We should always get data in JSON format, that's what Elementor saves it as. In tests
|
||||
// we've seen it stored as a serialized array in a few cases. Elementor can read both.
|
||||
// For good measure we test and convert if needed.
|
||||
if ( is_array( $original_json ) ) {
|
||||
$original_json = wp_json_encode( $original_json );
|
||||
if ( false === $original_json ) {
|
||||
$error_msg = sprintf( __( 'Existing elementor data for post - %d contains a serialized array that could not be converted to JSON - skipping', 'amazon-s3-and-cloudfront' ), $post_id );
|
||||
AS3CF_Error::log( $error_msg );
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Verify that the original post meta contains valid JSON
|
||||
if ( is_string( $original_json ) ) {
|
||||
$decoded_original = json_decode( $original_json, true );
|
||||
if ( is_null( $decoded_original ) ) {
|
||||
$error_msg = sprintf( __( 'Existing elementor data for post - %d contains invalid JSON - skipping', 'amazon-s3-and-cloudfront' ), $post_id );
|
||||
AS3CF_Error::log( $error_msg );
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$modified_json = $original_json;
|
||||
$modified_json = str_replace( '\/', '/', $modified_json );
|
||||
$modified_json = $this->as3cf->filter_provider->filter_post( $modified_json );
|
||||
|
||||
// Verify that we still have valid JSON
|
||||
$decoded = json_decode( $modified_json, true );
|
||||
if ( is_null( $decoded ) ) {
|
||||
$error_msg = sprintf( __( 'Error replacing URLs in Elementor data for post - %d results in invalid JSON', 'amazon-s3-and-cloudfront' ), $post_id );
|
||||
AS3CF_Error::log( $error_msg );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Verify that the JSON can be re-encoded
|
||||
$modified_json = wp_json_encode( $decoded );
|
||||
if ( false === $modified_json ) {
|
||||
$error_msg = sprintf( __( 'Error replacing URLs in Elementor data for post - %d JSON re-encoding failed', 'amazon-s3-and-cloudfront' ), $post_id );
|
||||
AS3CF_Error::log( $error_msg );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( $modified_json !== $original_json ) {
|
||||
update_post_meta( $post_id, '_elementor_data', wp_slash( $modified_json ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the count of Elementor posts
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_elementor_items_count() {
|
||||
return $this->get_blog_items( Media_Library_Item::source_type(), null, null, true );
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all items in the Posts table that are created with Elementor
|
||||
*
|
||||
* @param string $source_type Item source type
|
||||
* @param int $last_post_id
|
||||
* @param int $limit Maximum number of posts to return
|
||||
* @param bool $count Just return the count, negates $limit, default false
|
||||
*
|
||||
* @return array|int
|
||||
*/
|
||||
protected function get_blog_items( $source_type, $last_post_id, $limit, $count = false ) {
|
||||
global $wpdb;
|
||||
|
||||
$args = array();
|
||||
|
||||
if ( $count ) {
|
||||
$sql = 'SELECT COUNT(DISTINCT p.id)';
|
||||
} else {
|
||||
$sql = 'SELECT DISTINCT p.id';
|
||||
}
|
||||
|
||||
$sql .= " FROM {$wpdb->posts} p LEFT JOIN {$wpdb->postmeta} m ON p.id = m.post_id";
|
||||
$sql .= " WHERE m.meta_key = '_elementor_data' AND p.post_status != 'inherit' ";
|
||||
|
||||
if ( is_numeric( $last_post_id ) ) {
|
||||
$sql .= ' AND p.id < %d';
|
||||
$args[] = $last_post_id;
|
||||
}
|
||||
|
||||
if ( ! $count ) {
|
||||
$sql .= ' ORDER BY p.id DESC LIMIT %d';
|
||||
$args[] = $limit;
|
||||
}
|
||||
|
||||
if ( count( $args ) > 0 ) {
|
||||
$sql = $wpdb->prepare( $sql, $args );
|
||||
}
|
||||
|
||||
if ( $count ) {
|
||||
return $wpdb->get_var( $sql );
|
||||
} else {
|
||||
return array_map( 'intval', $wpdb->get_col( $sql ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been cancelled.
|
||||
*/
|
||||
protected function cancelled() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been paused.
|
||||
*/
|
||||
protected function paused() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been resumed.
|
||||
*/
|
||||
protected function resumed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has completed.
|
||||
*/
|
||||
protected function completed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete notice message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_complete_message() {
|
||||
return __( 'Finished the Elementor Analyze and Repair process.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
}
|
||||
456
classes/pro/background-processes/move-objects-process.php
Normal file
456
classes/pro/background-processes/move-objects-process.php
Normal file
@@ -0,0 +1,456 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Background_Processes;
|
||||
|
||||
use AS3CF_Error;
|
||||
use AS3CF_Utils;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Providers\Storage\Storage_Provider;
|
||||
use Exception;
|
||||
|
||||
class Move_Objects_Process extends Background_Tool_Process {
|
||||
|
||||
const MOVE_NO = 0;
|
||||
const MOVE_YES = 1;
|
||||
const MOVE_SAME = 2;
|
||||
const MOVE_NOOP = 3;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'move_objects';
|
||||
|
||||
/**
|
||||
* Process items chunk.
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param array $source_ids
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function process_items_chunk( $source_type, $source_ids, $blog_id ) {
|
||||
/** @var Item $class */
|
||||
$class = $this->as3cf->get_source_type_class( $source_type );
|
||||
$items_to_move = array();
|
||||
|
||||
if ( $this->as3cf->private_prefix_enabled() ) {
|
||||
$new_private_prefix = $this->as3cf->get_setting( 'signed-urls-object-prefix', '' );
|
||||
} else {
|
||||
$new_private_prefix = '';
|
||||
}
|
||||
|
||||
foreach ( $source_ids as $source_id ) {
|
||||
$update = false;
|
||||
$as3cf_item = $class::get_by_source_id( $source_id );
|
||||
|
||||
if ( $as3cf_item ) {
|
||||
// Analyze current path to see if it needs changing.
|
||||
$old_prefix = $as3cf_item->normalized_path_dir();
|
||||
$new_prefix = $this->get_new_public_prefix( $as3cf_item, $old_prefix );
|
||||
|
||||
if ( $new_prefix !== $old_prefix ) {
|
||||
$update = true;
|
||||
}
|
||||
|
||||
// Analyze current private prefix to see if it needs changing.
|
||||
$private_prefix = $as3cf_item->private_prefix();
|
||||
|
||||
switch ( $this->should_move_to_new_private_prefix( $as3cf_item, $private_prefix, $new_private_prefix ) ) {
|
||||
case self::MOVE_NO:
|
||||
case self::MOVE_SAME:
|
||||
break;
|
||||
case self::MOVE_NOOP:
|
||||
// If nothing is to be moved to new private prefix, and public isn't being updated, just fix data.
|
||||
if ( false === $update ) {
|
||||
$as3cf_item->set_private_prefix( $new_private_prefix );
|
||||
$as3cf_item->save();
|
||||
continue 2;
|
||||
}
|
||||
|
||||
$private_prefix = $new_private_prefix;
|
||||
break;
|
||||
case self::MOVE_YES:
|
||||
$private_prefix = $new_private_prefix;
|
||||
$update = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( $update ) {
|
||||
$items_to_move[ $source_id ] = array( 'prefix' => $new_prefix, 'private_prefix' => $private_prefix );
|
||||
}
|
||||
} else {
|
||||
$name = $this->as3cf->get_source_type_name( $source_type );
|
||||
AS3CF_Error::log( sprintf( 'Move Objects: Offload data for %s item with ID %d could not be found for analysis.', $name, $source_id ) );
|
||||
}
|
||||
}
|
||||
|
||||
$this->move_items( $items_to_move, $blog_id, $source_type );
|
||||
|
||||
// Whether moved or not, we processed every item.
|
||||
return $source_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns new public prefix if required, otherwise returns old prefix.
|
||||
*
|
||||
* phpcs:disable Generic.PHP.DiscourageGoto.Found
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param string $old_prefix
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_new_public_prefix( Item $as3cf_item, $old_prefix ) {
|
||||
$new_prefix = $as3cf_item->get_new_item_prefix();
|
||||
|
||||
// Length changed is simplest indicator.
|
||||
if ( strlen( $old_prefix ) !== strlen( $new_prefix ) ) {
|
||||
goto move_item;
|
||||
}
|
||||
|
||||
$old_parts = explode( '/', trim( $old_prefix, '/' ) );
|
||||
$new_parts = explode( '/', trim( $new_prefix, '/' ) );
|
||||
|
||||
// Number of path elements changed?
|
||||
if ( count( $old_parts ) !== count( $new_parts ) ) {
|
||||
goto move_item;
|
||||
}
|
||||
|
||||
// If object versioning is on, don't compare last segment.
|
||||
if ( $this->as3cf->get_setting( 'object-versioning', false ) && $as3cf_item->can_use_object_versioning() ) {
|
||||
$old_parts = array_slice( $old_parts, 0, -1 );
|
||||
$new_parts = array_slice( $new_parts, 0, -1 );
|
||||
}
|
||||
|
||||
// Each element should now be the same.
|
||||
// Simplest way to check here is walk one and check the other by index.
|
||||
// No need to get all fancy!
|
||||
foreach ( $old_parts as $key => $val ) {
|
||||
if ( $new_parts[ $key ] !== $val ) {
|
||||
goto move_item;
|
||||
}
|
||||
}
|
||||
|
||||
// If here, then prefix does not need to change, regardless of whether private prefix does.
|
||||
// This could be important for mixed public/private thumbnails and external links.
|
||||
// We already know that old and new prefix are the same except for object version,
|
||||
// which is at least still using the same format (length check confirmed that).
|
||||
$new_prefix = $old_prefix;
|
||||
|
||||
move_item:
|
||||
|
||||
return $new_prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the given item be moved to the new private prefix?
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param string $old_private_prefix
|
||||
* @param string $new_private_prefix
|
||||
*
|
||||
* @return int One of MOVE_NO, MOVE_YES, MOVE_SAME or MOVE_NOOP.
|
||||
*/
|
||||
protected function should_move_to_new_private_prefix( Item $as3cf_item, $old_private_prefix, $new_private_prefix ) {
|
||||
// Analyze current private prefix to see if it needs changing.
|
||||
if ( $old_private_prefix === $new_private_prefix ) {
|
||||
// Private prefix not changed, nothing to do.
|
||||
return self::MOVE_SAME;
|
||||
} elseif ( ! $as3cf_item->is_private() && ! $as3cf_item->has_private_objects() ) {
|
||||
// Not same, but nothing is to be moved to private prefix, maybe just fix data.
|
||||
return self::MOVE_NOOP;
|
||||
} else {
|
||||
// Private prefix changed, move some private objects.
|
||||
return self::MOVE_YES;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move items to new path.
|
||||
*
|
||||
* @param array $items id => ['prefix' => 'new/path/prefix', 'private_prefix' => 'private']
|
||||
* @param int $blog_id
|
||||
* @param string $source_type
|
||||
*
|
||||
* @throws Exception
|
||||
*
|
||||
* Note: `private_prefix` will be prepended to `prefix` for any object that is private.
|
||||
* `prefix` and `private_prefix` are "directory" paths and can have leading/trailing slashes, they'll be handled.
|
||||
* Both `prefix` and `private_prefix` must be set per item id, but either/both may be empty.
|
||||
*/
|
||||
protected function move_items( $items, $blog_id, $source_type ) {
|
||||
if ( empty( $items ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$bucket = $this->as3cf->get_setting( 'bucket' );
|
||||
$region = $this->as3cf->get_setting( 'region' );
|
||||
|
||||
if ( empty( $bucket ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Item $class */
|
||||
$class = $this->as3cf->get_source_type_class( $source_type );
|
||||
$source_type_name = $this->as3cf->get_source_type_name( $source_type );
|
||||
$keys = array();
|
||||
$new_keys = array();
|
||||
$items_to_move = array();
|
||||
|
||||
foreach ( array_keys( $items ) as $source_id ) {
|
||||
/** @var Item $as3cf_item */
|
||||
$as3cf_item = $class::get_by_source_id( $source_id );
|
||||
$source_keys = array_unique( $as3cf_item->provider_keys() );
|
||||
|
||||
// If the item isn't served by this provider, skip it.
|
||||
if ( ! $as3cf_item->served_by_provider( true ) ) {
|
||||
$error_msg = sprintf( __( '% ID %s is offloaded to a different provider than currently configured', 'amazon-s3-and-cloudfront' ), $source_type_name, $source_id );
|
||||
$this->record_error( $blog_id, $source_type, $source_id, $error_msg );
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the prefix isn't set, skip it.
|
||||
if ( ! isset( $items[ $source_id ]['prefix'] ) ) {
|
||||
$error_msg = sprintf( __( 'Prefix not set for % ID %s (this should never happen, please report to support)', 'amazon-s3-and-cloudfront' ), $source_type_name, $source_id );
|
||||
$this->record_error( $blog_id, $source_type, $source_id, $error_msg );
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the private_prefix isn't set, skip it.
|
||||
if ( ! isset( $items[ $source_id ]['private_prefix'] ) ) {
|
||||
$error_msg = sprintf( __( 'Private prefix not set for %s ID %s (this should never happen, please report to support)', 'amazon-s3-and-cloudfront' ), $source_type_name, $source_id );
|
||||
$this->record_error( $blog_id, $source_type, $source_id, $error_msg );
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the item is offloaded to another bucket, skip it.
|
||||
if ( $as3cf_item->bucket() !== $bucket ) {
|
||||
$error_msg = sprintf( __( '%s ID %s is offloaded to a different bucket than currently configured', 'amazon-s3-and-cloudfront' ), $source_type_name, $source_id );
|
||||
$this->record_error( $blog_id, $source_type, $source_id, $error_msg );
|
||||
continue;
|
||||
}
|
||||
|
||||
$updated_item = clone $as3cf_item;
|
||||
|
||||
$updated_item->set_private_prefix( AS3CF_Utils::trailingslash_prefix( $items[ $source_id ]['private_prefix'] ) );
|
||||
$updated_item->update_path_prefix( AS3CF_Utils::trailingslash_prefix( $items[ $source_id ]['prefix'] ) );
|
||||
|
||||
// TODO: Make sure we're not clobbering another item's path.
|
||||
|
||||
// Each key found in old paths will be moved to new path as appropriate for access.
|
||||
foreach ( $source_keys as $object_key => $key ) {
|
||||
$new_key = $updated_item->provider_key( $object_key );
|
||||
|
||||
// If the old and new key are the same, don't try and move it.
|
||||
if ( $key === $new_key ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// We need to record the old and new key so that we can reconcile them later.
|
||||
$keys[ $source_id ][] = $key;
|
||||
$new_keys[ $source_id ][] = $new_key;
|
||||
|
||||
$args = array(
|
||||
'Bucket' => $as3cf_item->bucket(),
|
||||
'Key' => $new_key,
|
||||
'CopySource' => urlencode( "{$as3cf_item->bucket()}/{$key}" ),
|
||||
);
|
||||
|
||||
$acl = $as3cf_item->get_acl_for_object_key( $object_key );
|
||||
|
||||
// Only set ACL if actually required, some storage provider and bucket settings disable changing ACL.
|
||||
if ( ! empty( $acl ) ) {
|
||||
$args['ACL'] = $acl;
|
||||
}
|
||||
|
||||
$args = Storage_Provider::filter_object_meta( $args, $as3cf_item, $object_key );
|
||||
|
||||
// Protect against filter use and only set ACL if actually required, some storage provider and bucket settings disable changing ACL.
|
||||
if ( isset( $args['ACL'] ) && empty( $acl ) ) {
|
||||
unset( $args['ACL'] );
|
||||
}
|
||||
|
||||
$items_to_move[] = $args;
|
||||
}
|
||||
}
|
||||
|
||||
// All skipped, abort.
|
||||
if ( empty( $items_to_move ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* As there is no such thing as "move" objects in supported providers, and we want to be able to roll-back
|
||||
* an entire item's copies if any fail, we copy, check for failures, and then only delete old keys
|
||||
* which have successfully copied. Any partially copied item have their successful copies deleted
|
||||
* instead so as to not leave orphaned objects either with old or new key prefixes.
|
||||
*/
|
||||
|
||||
$client = $this->as3cf->get_provider_client( $region, true );
|
||||
|
||||
try {
|
||||
$failures = $client->copy_objects( $items_to_move );
|
||||
} catch ( Exception $e ) {
|
||||
AS3CF_Error::log( $e->getMessage() );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! empty( $failures ) ) {
|
||||
$keys_to_remove = $this->handle_failed_keys( $keys, $failures, $blog_id, $source_type, $new_keys );
|
||||
} else {
|
||||
$keys_to_remove = $keys;
|
||||
}
|
||||
|
||||
// Prepare and batch delete all the redundant keys.
|
||||
$objects_to_delete = array();
|
||||
|
||||
foreach ( $keys_to_remove as $source_id => $objects ) {
|
||||
foreach ( $objects as $idx => $object ) {
|
||||
// If key was not moved, don't delete it.
|
||||
if ( in_array( $object, $keys[ $source_id ] ) && in_array( $object, $new_keys[ $source_id ] ) ) {
|
||||
unset( $keys_to_remove[ $source_id ][ $idx ] );
|
||||
continue;
|
||||
}
|
||||
|
||||
$objects_to_delete[] = array( 'Key' => $object );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $objects_to_delete ) ) {
|
||||
try {
|
||||
$client->delete_objects( array(
|
||||
'Bucket' => $bucket,
|
||||
'Delete' => array(
|
||||
'Objects' => $objects_to_delete,
|
||||
),
|
||||
) );
|
||||
} catch ( Exception $e ) {
|
||||
AS3CF_Error::log( $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
$this->update_item_provider_info( $keys, $new_keys, $keys_to_remove, $items, $source_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle failed keys.
|
||||
*
|
||||
* @param array $keys id => ['path1', 'path2', ...]
|
||||
* @param array $failures [] => ['Key', 'Message']
|
||||
* @param int $blog_id
|
||||
* @param string $source_type
|
||||
* @param array $new_keys id => ['path1', 'path2', ...]
|
||||
*
|
||||
* @return array Keys that can be removed, old and new (roll-back)
|
||||
*/
|
||||
protected function handle_failed_keys( $keys, $failures, $blog_id, $source_type, $new_keys ) {
|
||||
foreach ( $failures as $failure ) {
|
||||
foreach ( $new_keys as $source_id => $source_keys ) {
|
||||
$key_id = array_search( $failure['Key'], $source_keys );
|
||||
|
||||
if ( false !== $key_id ) {
|
||||
$error_msg = sprintf(
|
||||
__( 'Error moving %1$s to %2$s for item %3$d: %4$s', 'amazon-s3-and-cloudfront' ),
|
||||
$keys[ $source_id ][ $key_id ],
|
||||
$failure['Key'],
|
||||
$source_id,
|
||||
$failure['Message']
|
||||
);
|
||||
|
||||
$this->record_error( $blog_id, $source_type, $source_id, $error_msg );
|
||||
|
||||
// Instead of deleting old keys for item, delete new ones (roll-back).
|
||||
$keys[ $source_id ] = $new_keys[ $source_id ];
|
||||
|
||||
// Prevent further errors being shown for aborted item.
|
||||
unset( $new_keys[ $source_id ] );
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update item provider info.
|
||||
*
|
||||
* @param array $keys id => ['path1', 'path2', ...]
|
||||
* @param array $new_keys id => ['path1', 'path2', ...]
|
||||
* @param array $removed_keys id => ['path1', 'path2', ...]
|
||||
* @param array $items id => ['prefix' => 'new/path/prefix', 'private_prefix' => 'private']
|
||||
* @param string $source_type
|
||||
*/
|
||||
protected function update_item_provider_info( $keys, $new_keys, $removed_keys, $items, $source_type ) {
|
||||
// There absolutely should be old keys, new keys, some removed/moved, and item prefix data.
|
||||
if ( empty( $keys ) || empty( $new_keys ) || empty( $removed_keys ) || empty( $items ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Item $class */
|
||||
$class = $this->as3cf->get_source_type_class( $source_type );
|
||||
|
||||
foreach ( $keys as $source_id => $source_keys ) {
|
||||
if ( empty( $items[ $source_id ] ) || empty( $new_keys[ $source_id ] ) || empty( $removed_keys[ $source_id ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// As long as none of the new keys have been removed (roll-back),
|
||||
// then we're all good to update the primary path and private prefix.
|
||||
if ( ! empty( array_intersect( $new_keys[ $source_id ], $removed_keys[ $source_id ] ) ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$as3cf_item = $class::get_by_source_id( $source_id );
|
||||
|
||||
$extra_info = $as3cf_item->extra_info();
|
||||
$extra_info['private_prefix'] = $items[ $source_id ]['private_prefix'];
|
||||
|
||||
$as3cf_item->set_extra_info( $extra_info );
|
||||
$as3cf_item->update_path_prefix( $items[ $source_id ]['prefix'] );
|
||||
$as3cf_item->save();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete notice message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_complete_message() {
|
||||
return __( 'Finished moving media files to new paths.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been cancelled.
|
||||
*/
|
||||
protected function cancelled() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been paused.
|
||||
*/
|
||||
protected function paused() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been resumed.
|
||||
*/
|
||||
protected function resumed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has completed.
|
||||
*/
|
||||
protected function completed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Background_Processes;
|
||||
|
||||
use AS3CF_Error;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use Exception;
|
||||
|
||||
class Move_Private_Objects_Process extends Move_Objects_Process {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'move_private_objects';
|
||||
|
||||
/**
|
||||
* Process items chunk.
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param array $source_ids
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function process_items_chunk( $source_type, $source_ids, $blog_id ) {
|
||||
/** @var Item $class */
|
||||
$class = $this->as3cf->get_source_type_class( $source_type );
|
||||
$items_to_move = array();
|
||||
|
||||
if ( $this->as3cf->private_prefix_enabled() ) {
|
||||
$new_private_prefix = $this->as3cf->get_setting( 'signed-urls-object-prefix' );
|
||||
} else {
|
||||
$new_private_prefix = '';
|
||||
}
|
||||
|
||||
foreach ( $source_ids as $source_id ) {
|
||||
$as3cf_item = $class::get_by_source_id( $source_id );
|
||||
|
||||
if ( $as3cf_item ) {
|
||||
// Analyze current private prefix to see if it needs changing.
|
||||
switch ( $this->should_move_to_new_private_prefix( $as3cf_item, $as3cf_item->private_prefix(), $new_private_prefix ) ) {
|
||||
case self::MOVE_NO:
|
||||
case self::MOVE_SAME:
|
||||
break;
|
||||
case self::MOVE_NOOP:
|
||||
// If nothing is to be moved to new private prefix, just fix data.
|
||||
$as3cf_item->set_private_prefix( $new_private_prefix );
|
||||
$as3cf_item->save();
|
||||
continue 2;
|
||||
case self::MOVE_YES:
|
||||
$items_to_move[ $source_id ] = array( 'prefix' => $as3cf_item->normalized_path_dir(), 'private_prefix' => $new_private_prefix );
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
$name = $this->as3cf->get_source_type_name( $source_type );
|
||||
AS3CF_Error::log( sprintf( 'Move Private Objects: Offload data for %s item with ID %d could not be found for analysis.', $name, $source_id ) );
|
||||
}
|
||||
}
|
||||
|
||||
$this->move_items( $items_to_move, $blog_id, $source_type );
|
||||
|
||||
// Whether moved or not, we processed every item.
|
||||
return $source_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete notice message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_complete_message() {
|
||||
return __( 'Finished moving media files to new private paths.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Background_Processes;
|
||||
|
||||
use AS3CF_Error;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use Exception;
|
||||
|
||||
class Move_Public_Objects_Process extends Move_Objects_Process {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'move_public_objects';
|
||||
|
||||
/**
|
||||
* Process items chunk.
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param array $source_ids
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function process_items_chunk( $source_type, $source_ids, $blog_id ) {
|
||||
$items_to_move = array();
|
||||
|
||||
/** @var Item $class */
|
||||
$class = $this->as3cf->get_source_type_class( $source_type );
|
||||
|
||||
foreach ( $source_ids as $source_id ) {
|
||||
$as3cf_item = $class::get_by_source_id( $source_id );
|
||||
|
||||
if ( $as3cf_item ) {
|
||||
// Analyze current path to see if it needs changing.
|
||||
$old_prefix = $as3cf_item->normalized_path_dir();
|
||||
$new_prefix = $this->get_new_public_prefix( $as3cf_item, $old_prefix );
|
||||
|
||||
if ( $new_prefix !== $old_prefix ) {
|
||||
$items_to_move[ $source_id ] = array( 'prefix' => $new_prefix, 'private_prefix' => $as3cf_item->private_prefix() );
|
||||
}
|
||||
} else {
|
||||
$name = $this->as3cf->get_source_type_name( $source_type );
|
||||
AS3CF_Error::log( sprintf( 'Move Public Objects: Offload data for %s item with ID %d could not be found for analysis.', $name, $source_id ) );
|
||||
}
|
||||
}
|
||||
|
||||
$this->move_items( $items_to_move, $blog_id, $source_type );
|
||||
|
||||
// Whether moved or not, we processed every item.
|
||||
return $source_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete notice message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_complete_message() {
|
||||
return __( 'Finished moving media files to new storage paths.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Background_Processes;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Remove_Local_Handler;
|
||||
use Exception;
|
||||
|
||||
class Remove_Local_Files_Process extends Background_Tool_Process {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'remove_local_files';
|
||||
|
||||
/**
|
||||
* Process items chunk.
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param array $source_ids
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function process_items_chunk( $source_type, $source_ids, $blog_id ) {
|
||||
$processed = $source_ids;
|
||||
$remove_local_handler = $this->as3cf->get_item_handler( Remove_Local_Handler::get_item_handler_key_name() );
|
||||
|
||||
/** @var Item $class */
|
||||
$class = $this->as3cf->get_source_type_class( $source_type );
|
||||
|
||||
foreach ( $source_ids as $source_id ) {
|
||||
/** @var Item $as3cf_item */
|
||||
$as3cf_item = $class::get_by_source_id( $source_id );
|
||||
|
||||
if ( empty( $as3cf_item ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! $as3cf_item->served_by_provider( true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! $as3cf_item->exists_locally() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$remove_local_handler->handle( $as3cf_item );
|
||||
}
|
||||
|
||||
// Whether removed from local or not, we processed every item.
|
||||
return $processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete notice message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_complete_message() {
|
||||
return __( 'Finished removing media files from local server.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been cancelled.
|
||||
*/
|
||||
protected function cancelled() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been paused.
|
||||
*/
|
||||
protected function paused() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been resumed.
|
||||
*/
|
||||
protected function resumed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has completed.
|
||||
*/
|
||||
protected function completed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
}
|
||||
185
classes/pro/background-processes/update-acls-process.php
Normal file
185
classes/pro/background-processes/update-acls-process.php
Normal file
@@ -0,0 +1,185 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Background_Processes;
|
||||
|
||||
use AS3CF_Error;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use Exception;
|
||||
|
||||
class Update_ACLs_Process extends Background_Tool_Process {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'update_acls';
|
||||
|
||||
/**
|
||||
* Process items chunk.
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param array $source_ids
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function process_items_chunk( $source_type, $source_ids, $blog_id ) {
|
||||
$bucket = $this->as3cf->get_setting( 'bucket' );
|
||||
$region = $this->as3cf->get_setting( 'region' );
|
||||
|
||||
/** @var Item $class */
|
||||
$class = $this->as3cf->get_source_type_class( $source_type );
|
||||
$attachments_to_update = array();
|
||||
|
||||
foreach ( $source_ids as $source_id ) {
|
||||
$as3cf_item = $class::get_by_source_id( $source_id );
|
||||
|
||||
if ( empty( $as3cf_item ) ) {
|
||||
$name = $this->as3cf->get_source_type_name( $source_type );
|
||||
AS3CF_Error::log( sprintf( 'Update Object ACLs: Offload data for %s item with ID %d could not be found for analysis.', $name, $source_id ) );
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the attachment is offloaded to another provider, skip it.
|
||||
if ( ! $as3cf_item->served_by_provider( true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the attachment is offloaded to another bucket, skip it, because we don't know its Block Public Access state.
|
||||
if ( $as3cf_item->bucket() !== $bucket ) {
|
||||
continue;
|
||||
}
|
||||
$attachments_to_update[] = $source_id;
|
||||
}
|
||||
|
||||
$this->update_items( $source_type, $attachments_to_update, $blog_id, $bucket, $region );
|
||||
|
||||
// Whether updated or not, we processed every item.
|
||||
return $source_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk update ACLs for items.
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param array $items
|
||||
* @param int $blog_id
|
||||
* @param string $bucket
|
||||
* @param string $region
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function update_items( $source_type, $items, $blog_id, $bucket, $region ) {
|
||||
if ( empty( $items ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$keys = $this->as3cf->get_provider_keys( $items, $source_type );
|
||||
|
||||
if ( empty( $keys ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var Item $class */
|
||||
$class = $this->as3cf->get_source_type_class( $source_type );
|
||||
$items_to_update = array();
|
||||
|
||||
foreach ( $keys as $source_id => $source_keys ) {
|
||||
$as3cf_item = $class::get_by_source_id( $source_id );
|
||||
|
||||
foreach ( $source_keys as $key ) {
|
||||
$size = $as3cf_item->get_object_key_from_filename( $key );
|
||||
$acl = $as3cf_item->get_acl_for_object_key( $size );
|
||||
|
||||
// Only set ACL if actually required, some storage provider and bucket settings disable changing ACL.
|
||||
// This is a fallback check, just in case settings changed from under us via define etc, saves throwing lots of errors.
|
||||
if ( ! empty( $acl ) ) {
|
||||
$items_to_update[] = array(
|
||||
'Bucket' => $bucket,
|
||||
'Key' => $key,
|
||||
'ACL' => $acl,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$failures = array();
|
||||
|
||||
if ( ! empty( $items_to_update ) ) {
|
||||
$client = $this->as3cf->get_provider_client( $region, true );
|
||||
try {
|
||||
$failures = $client->update_object_acls( $items_to_update );
|
||||
} catch ( Exception $e ) {
|
||||
AS3CF_Error::log( $e->getMessage() );
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $failures ) ) {
|
||||
$this->record_failures( $keys, $failures, $blog_id, $source_type );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle failed keys.
|
||||
*
|
||||
* @param array $keys
|
||||
* @param array $failures
|
||||
* @param int $blog_id
|
||||
* @param string $source_type
|
||||
*/
|
||||
protected function record_failures( $keys, $failures, $blog_id, $source_type ) {
|
||||
foreach ( $failures as $failure ) {
|
||||
foreach ( $keys as $source_id => $source_keys ) {
|
||||
if ( false !== array_search( $failure['Key'], $source_keys ) ) {
|
||||
$error_msg = sprintf( __( 'Error updating object ACL for %1$s: %2$s', 'amazon-s3-and-cloudfront' ), $failure['Key'], $failure['Message'] );
|
||||
|
||||
$this->record_error( $blog_id, $source_type, $source_id, $error_msg );
|
||||
|
||||
unset( $keys[ $source_id ] );
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete notice message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_complete_message() {
|
||||
return __( 'Finished updating object ACLs in bucket.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been cancelled.
|
||||
*/
|
||||
protected function cancelled() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been paused.
|
||||
*/
|
||||
protected function paused() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been resumed.
|
||||
*/
|
||||
protected function resumed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has completed.
|
||||
*/
|
||||
protected function completed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
}
|
||||
225
classes/pro/background-processes/uploader-process.php
Normal file
225
classes/pro/background-processes/uploader-process.php
Normal file
@@ -0,0 +1,225 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Background_Processes;
|
||||
|
||||
use Amazon_S3_And_CloudFront;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Upload_Handler;
|
||||
use Exception;
|
||||
|
||||
class Uploader_Process extends Background_Tool_Process {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'uploader';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $license_limit = -1;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $offloaded = 0;
|
||||
|
||||
/**
|
||||
* Process items chunk.
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param array $source_ids
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function process_items_chunk( $source_type, $source_ids, $blog_id ) {
|
||||
$processed = array();
|
||||
|
||||
// With caching this may be some minutes behind, and may not include previous batches,
|
||||
// but this really doesn't matter in the grand scheme of things as it'll eventually catch up.
|
||||
$this->license_limit = $this->as3cf->get_total_allowed_media_items_to_upload();
|
||||
|
||||
foreach ( $source_ids as $source_id ) {
|
||||
// Check we are allowed to carry on offloading.
|
||||
if ( ! $this->should_upload_item( $source_id, $blog_id ) ) {
|
||||
return $processed;
|
||||
}
|
||||
|
||||
if ( $this->handle_item( $source_type, $source_id, $blog_id ) ) {
|
||||
$this->offloaded++;
|
||||
}
|
||||
|
||||
// Whether actually offloaded or not, we've processed the item.
|
||||
$processed[] = $source_id;
|
||||
}
|
||||
|
||||
return $processed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload the item to provider.
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param int $source_id
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function handle_item( $source_type, $source_id, $blog_id ) {
|
||||
/** @var Item $class */
|
||||
$class = $this->as3cf->get_source_type_class( $source_type );
|
||||
$as3cf_item = $class::get_by_source_id( $source_id );
|
||||
|
||||
// Skip item if item already on provider.
|
||||
if ( $as3cf_item ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Skip if we can't get a valid Item instance.
|
||||
$as3cf_item = $class::create_from_source_id( $source_id );
|
||||
if ( is_wp_error( $as3cf_item ) ) {
|
||||
$this->record_error( $blog_id, $source_type, $source_id, $as3cf_item->get_error_message() );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$upload_handler = $this->as3cf->get_item_handler( Upload_Handler::get_item_handler_key_name() );
|
||||
$upload_result = $upload_handler->handle( $as3cf_item );
|
||||
|
||||
// Build error message.
|
||||
if ( is_wp_error( $upload_result ) ) {
|
||||
if ( $this->count_errors() < 100 ) {
|
||||
foreach ( $upload_result->get_error_messages() as $error_message ) {
|
||||
$error_msg = sprintf( __( 'Error offloading to bucket - %s', 'amazon-s3-and-cloudfront' ), $error_message );
|
||||
$this->record_error( $blog_id, $source_type, $source_id, $error_msg );
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check there is enough allowed items for the license before uploading.
|
||||
*
|
||||
* @param int $item_id
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function should_upload_item( $item_id, $blog_id ) {
|
||||
// No limit, or not counting towards limit.
|
||||
if ( 0 > $this->license_limit ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// If media limit met, cancel the offload and give notice.
|
||||
if ( 0 >= ( $this->license_limit - $this->offloaded ) ) {
|
||||
// Be really, really sure!
|
||||
$this->as3cf->update_media_library_total();
|
||||
$this->license_limit = $this->as3cf->get_total_allowed_media_items_to_upload();
|
||||
|
||||
if ( 0 === $this->license_limit ) {
|
||||
$this->cancel();
|
||||
|
||||
$notice_id = $this->tool->get_tool_key() . '_license_limit';
|
||||
|
||||
$this->as3cf->notices->undismiss_notice_for_all( $notice_id );
|
||||
|
||||
$args = array(
|
||||
'custom_id' => $notice_id,
|
||||
'flash' => false,
|
||||
'only_show_to_user' => false,
|
||||
);
|
||||
|
||||
$this->as3cf->notices->add_notice( $this->get_reached_license_limit_message(), $args );
|
||||
|
||||
return false;
|
||||
} else {
|
||||
// Carry on!
|
||||
$this->offloaded = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get reached license limit notice message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_reached_license_limit_message() {
|
||||
$account_link = sprintf( '<a href="%s" target="_blank">%s</a>', $this->as3cf->get_my_account_url(), __( 'My Account', 'amazon-s3-and-cloudfront' ) );
|
||||
$notice_msg = __( "You've reached your license limit so we've had to stop your offload. To offload the rest of your media, please upgrade your license from %s and simply start the offload again. It will start from where it stopped.", 'amazon-s3-and-cloudfront' );
|
||||
|
||||
return sprintf( $notice_msg, $account_link );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get blog items to process.
|
||||
*
|
||||
* @param string $source_type Item source type
|
||||
* @param int $last_source_id
|
||||
* @param int $limit Maximum number of item IDs to return
|
||||
* @param bool $count Just return the count, negates $limit, default false
|
||||
*
|
||||
* @return array|int
|
||||
*/
|
||||
protected function get_blog_items( $source_type, $last_source_id, $limit, $count = false ) {
|
||||
/** @var Amazon_S3_And_CloudFront $as3cf */
|
||||
global $as3cf;
|
||||
|
||||
/** @var Item $class */
|
||||
$class = $as3cf->get_source_type_class( $source_type );
|
||||
|
||||
if ( ! empty( $class ) ) {
|
||||
return $class::get_missing_source_ids( $last_source_id, $limit, $count );
|
||||
}
|
||||
|
||||
return $count ? 0 : array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been cancelled.
|
||||
*/
|
||||
protected function cancelled() {
|
||||
$this->as3cf->update_media_library_total();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been paused.
|
||||
*/
|
||||
protected function paused() {
|
||||
$this->as3cf->update_media_library_total();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been resumed.
|
||||
*/
|
||||
protected function resumed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has completed.
|
||||
*/
|
||||
protected function completed() {
|
||||
$this->as3cf->update_media_library_total();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete notice message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_complete_message() {
|
||||
return __( 'Finished offloading media to bucket.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Background_Processes;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Integrations\Media_Library;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Integrations\Woocommerce;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Items\Update_Acl_Handler;
|
||||
use Exception;
|
||||
|
||||
class Woocommerce_Product_Urls_Process extends Background_Tool_Process {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'woocommerce_product_url';
|
||||
|
||||
/**
|
||||
* Process chunk of products
|
||||
*
|
||||
* @param string $source_type
|
||||
* @param array $source_ids
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function process_items_chunk( $source_type, $source_ids, $blog_id ) {
|
||||
foreach ( $source_ids as $source_id ) {
|
||||
$this->handle_attachment( $source_id, $blog_id );
|
||||
}
|
||||
|
||||
// We processed every item.
|
||||
return $source_ids;
|
||||
}
|
||||
|
||||
/**
|
||||
* Process individual product or product variations by looking for downloadable
|
||||
* files directly in the products meta data.
|
||||
*
|
||||
* @param int $product_id
|
||||
* @param int $blog_id
|
||||
*
|
||||
* @return bool
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function handle_attachment( $product_id, $blog_id ) {
|
||||
/** @var Media_Library $media_library */
|
||||
$media_library = $this->as3cf->get_integration_manager()->get_integration( 'mlib' );
|
||||
$woocommerce = new Woocommerce( $this->as3cf );
|
||||
|
||||
$acl_handler = $this->as3cf->get_item_handler( Update_Acl_Handler::get_item_handler_key_name() );
|
||||
|
||||
// Get all the downloadable files for this post. Straight
|
||||
// from the DB to avoid filters
|
||||
$downloads = get_post_meta( $product_id, '_downloadable_files', true );
|
||||
|
||||
// If we don't get an array, there's nothing we can do with
|
||||
// this product / variation
|
||||
if ( ! $downloads || ! is_array( $downloads ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$updated = false;
|
||||
|
||||
foreach ( $downloads as &$download ) {
|
||||
$stored_file = $download['file'];
|
||||
$size = null;
|
||||
$update_needed = false;
|
||||
|
||||
// Is this our shortcode?
|
||||
$attachment_id = $woocommerce->get_attachment_id_from_shortcode( $stored_file );
|
||||
if ( $attachment_id ) {
|
||||
$atts = $woocommerce->get_shortcode_atts( $stored_file );
|
||||
$update_needed = isset( $atts['id'] );
|
||||
}
|
||||
|
||||
// Is this a local URL?
|
||||
if ( false === $attachment_id ) {
|
||||
$attachment_id = $media_library->get_attachment_id_from_local_url( $stored_file );
|
||||
}
|
||||
|
||||
// Is it a remote URL we recognize?
|
||||
if ( false === $attachment_id ) {
|
||||
$attachment_id = $media_library->get_attachment_id_from_provider_url( $stored_file );
|
||||
$update_needed = true;
|
||||
}
|
||||
|
||||
// If we can't identify an offloaded item we have to give up
|
||||
if ( false === $attachment_id ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
|
||||
|
||||
// We couldn't find an item for this $attachment_id
|
||||
if ( ! $as3cf_item ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Ensure that it's private on the provider
|
||||
$size = $this->as3cf->filter_local->get_size_string_from_url( $as3cf_item->get_item_source_array(), $stored_file );
|
||||
|
||||
$options = array(
|
||||
'object_keys' => array( $size ),
|
||||
'set_private' => true,
|
||||
);
|
||||
$result = $acl_handler->handle( $as3cf_item, $options );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$error_msg = sprintf( __( 'Error updating object ACL for media library item %s', 'amazon-s3-and-cloudfront' ), $attachment_id );
|
||||
$this->record_error( $blog_id, Media_Library_Item::source_type(), $attachment_id, $error_msg );
|
||||
}
|
||||
|
||||
// If this is not a local URL already, we update:
|
||||
if ( $update_needed ) {
|
||||
$download['file'] = $as3cf_item->get_local_url( $size );
|
||||
$updated = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $updated ) {
|
||||
update_post_meta( $product_id, '_downloadable_files', $downloads );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find all product and/or product variation IDs that have a _downloadable_files
|
||||
* meta data item.
|
||||
*
|
||||
* @param string $source_type Item source type
|
||||
* @param int $last_product_id The ID of the last item previously processed
|
||||
* @param int $limit Maximum number of product IDs to return
|
||||
* @param bool $count Just return the count, negates $limit, default false
|
||||
*
|
||||
* @return array|int
|
||||
*/
|
||||
protected function get_blog_items( $source_type, $last_product_id, $limit, $count = false ) {
|
||||
global $wpdb;
|
||||
|
||||
$args = array();
|
||||
|
||||
if ( $count ) {
|
||||
$sql = 'SELECT COUNT(DISTINCT post_id)';
|
||||
} else {
|
||||
$sql = 'SELECT DISTINCT post_id';
|
||||
}
|
||||
|
||||
$sql .= " FROM {$wpdb->postmeta} ";
|
||||
$sql .= " WHERE meta_key='_downloadable_files' ";
|
||||
|
||||
if ( ! empty( $last_product_id ) ) {
|
||||
$sql .= ' AND post_id < %d';
|
||||
$args[] = $last_product_id;
|
||||
}
|
||||
|
||||
if ( ! $count ) {
|
||||
$sql .= ' ORDER BY post_id DESC LIMIT %d';
|
||||
$args[] = $limit;
|
||||
}
|
||||
|
||||
if ( count( $args ) > 0 ) {
|
||||
$sql = $wpdb->prepare( $sql, $args );
|
||||
}
|
||||
|
||||
if ( $count ) {
|
||||
return $wpdb->get_var( $sql );
|
||||
} else {
|
||||
return array_map( 'intval', $wpdb->get_col( $sql ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been cancelled.
|
||||
*/
|
||||
protected function cancelled() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been paused.
|
||||
*/
|
||||
protected function paused() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been resumed.
|
||||
*/
|
||||
protected function resumed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has completed.
|
||||
*/
|
||||
protected function completed() {
|
||||
// Do nothing at the moment.
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete notice message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_complete_message() {
|
||||
return __( 'Finished updating and verifying WooCommerce downloads.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user