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:
4790
classes/amazon-s3-and-cloudfront.php
Normal file
4790
classes/amazon-s3-and-cloudfront.php
Normal file
File diff suppressed because it is too large
Load Diff
87
classes/api/api-manager.php
Normal file
87
classes/api/api-manager.php
Normal file
@@ -0,0 +1,87 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\API;
|
||||
|
||||
class API_Manager {
|
||||
|
||||
/**
|
||||
* @var API_Manager
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $api_endpoints;
|
||||
|
||||
/**
|
||||
* Protected constructor to prevent creating a new instance of the
|
||||
* class via the `new` operator from outside this class.
|
||||
*/
|
||||
protected function __construct() {
|
||||
$this->api_endpoints = array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Make this class a singleton.
|
||||
*
|
||||
* Use this instead of __construct().
|
||||
*
|
||||
* @return API_Manager
|
||||
*/
|
||||
public static function get_instance(): API_Manager {
|
||||
if ( ! isset( static::$instance ) && ! ( self::$instance instanceof API_Manager ) ) {
|
||||
static::$instance = new API_Manager();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a named API Endpoint handler instance.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return API|null
|
||||
*/
|
||||
public function get_api_endpoint( string $name ): ?API {
|
||||
if ( ! empty( $name ) && array_key_exists( $name, $this->api_endpoints ) ) {
|
||||
return $this->api_endpoints[ $name ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of APi Endpoint paths keyed by their name.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function api_endpoints(): array {
|
||||
return array_map( function ( API $api_endpoint ) {
|
||||
return $api_endpoint->endpoint();
|
||||
}, $this->api_endpoints );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register API Endpoint handler instance.
|
||||
*
|
||||
* @param string $name
|
||||
* @param API $api
|
||||
*/
|
||||
public function register_api_endpoint( string $name, API $api ) {
|
||||
$this->api_endpoints[ $name ] = $api;
|
||||
}
|
||||
|
||||
/**
|
||||
* As this class is a singleton it should not be clone-able.
|
||||
*/
|
||||
protected function __clone() {
|
||||
}
|
||||
|
||||
/**
|
||||
* As this class is a singleton it should not be able to be unserialized.
|
||||
*/
|
||||
public function __wakeup() {
|
||||
}
|
||||
}
|
||||
160
classes/api/api.php
Normal file
160
classes/api/api.php
Normal file
@@ -0,0 +1,160 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\API;
|
||||
|
||||
use Amazon_S3_And_CloudFront;
|
||||
use WP_Error;
|
||||
use WP_HTTP_Response;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
abstract class API {
|
||||
const NAMESPACE_BASE = 'wp-offload-media';
|
||||
|
||||
/**
|
||||
* API Version that class is part of.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected static $version = 0;
|
||||
|
||||
/**
|
||||
* The endpoint name.
|
||||
*
|
||||
* This name will be used in the route, and where possible should be plural.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $name = '';
|
||||
|
||||
/** @var Amazon_S3_And_CloudFront */
|
||||
protected $as3cf;
|
||||
|
||||
/**
|
||||
* Initiate the API
|
||||
*
|
||||
* @param Amazon_S3_And_CloudFront $as3cf
|
||||
*/
|
||||
public function __construct( $as3cf ) {
|
||||
$this->as3cf = $as3cf;
|
||||
|
||||
add_action( 'rest_api_init', array( $this, 'register_routes' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API version.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public static function version() {
|
||||
return static::$version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name for endpoint.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function name() {
|
||||
return static::$name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API namespace for endpoint.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function api_namespace() {
|
||||
return self::NAMESPACE_BASE . '/v' . static::version();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get route to be appended to namespace in endpoint.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function route() {
|
||||
return '/' . static::name() . '/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get complete API endpoint path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function endpoint() {
|
||||
return static::api_namespace() . static::route();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get common response values for named API endpoint.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function endpoint_common_response( $name ) {
|
||||
$endpoint = $this->as3cf->get_api_manager()->get_api_endpoint( $name );
|
||||
|
||||
if ( ! empty( $endpoint ) ) {
|
||||
return $endpoint->common_response();
|
||||
}
|
||||
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Common response values for this API endpoint.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function common_response() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* All API requests must be from administrator or user granted "manage_options" capability.
|
||||
*
|
||||
* This function is used as the "permission_callback" for all routes.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function check_permissions( WP_REST_Request $request ) {
|
||||
return current_user_can( 'manage_options' );
|
||||
}
|
||||
|
||||
/**
|
||||
* A simple wrapper for rest_ensure_response to allow filters to fire before the response is sent.
|
||||
*
|
||||
* Ensures a REST response is a response object (for consistency).
|
||||
*
|
||||
* @param string $method Case-insensitive HTTP method, e.g. "get" or "post".
|
||||
* @param string $endpoint Endpoint name, e.g. "settings".
|
||||
* @param WP_Error|WP_HTTP_Response|mixed $response Response to check.
|
||||
* @param string $prefix Optional plugin prefix to be used for filter.
|
||||
*
|
||||
* @return WP_REST_Response|mixed If response generated an error, WP_Error, if response
|
||||
* is already an instance, WP_HTTP_Response, otherwise
|
||||
* returns a new WP_REST_Response instance.
|
||||
*/
|
||||
protected function rest_ensure_response( $method, $endpoint, $response, $prefix = '' ) {
|
||||
$method = empty( $method ) ? 'method' : strtolower( trim( $method ) );
|
||||
$endpoint = empty( $endpoint ) ? 'endpoint' : strtolower( trim( $endpoint ) );
|
||||
$prefix = empty( $prefix ) ? $this->as3cf->get_plugin_prefix() : strtolower( trim( $prefix ) );
|
||||
|
||||
// A specific filter is fed through a general filter to allow for granular and general filtering of responses.
|
||||
return rest_ensure_response(
|
||||
apply_filters(
|
||||
$prefix . '_api_response',
|
||||
apply_filters( $prefix . '_api_response_' . $method . '_' . $endpoint, $response )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register REST API routes.
|
||||
*/
|
||||
abstract public function register_routes();
|
||||
}
|
||||
475
classes/api/v1/buckets.php
Normal file
475
classes/api/v1/buckets.php
Normal file
@@ -0,0 +1,475 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\API\V1;
|
||||
|
||||
use AS3CF_Error;
|
||||
use DeliciousBrains\WP_Offload_Media\API\API;
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
class Buckets extends API {
|
||||
/** @var int */
|
||||
protected static $version = 1;
|
||||
|
||||
/** @var string */
|
||||
protected static $name = 'buckets';
|
||||
|
||||
/** @var array */
|
||||
private $error_args = array(
|
||||
'type' => 'error',
|
||||
'only_show_in_settings' => true,
|
||||
'only_show_on_tab' => 'media',
|
||||
'custom_id' => 'bucket-error',
|
||||
);
|
||||
|
||||
/**
|
||||
* Register REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route(),
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'get_buckets' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route(),
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( $this, 'post_buckets' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route(),
|
||||
array(
|
||||
'methods' => 'PUT',
|
||||
'callback' => array( $this, 'put_buckets' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a REST GET request and returns the current buckets, optionally for given region.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response|mixed
|
||||
*/
|
||||
public function get_buckets( WP_REST_Request $request ) {
|
||||
$data = $request->get_query_params();
|
||||
$region = empty( $data['region'] ) ? false : $data['region'];
|
||||
|
||||
$buckets = $this->as3cf->get_buckets( $region );
|
||||
|
||||
if ( is_wp_error( $buckets ) ) {
|
||||
$this->as3cf->notices->add_notice(
|
||||
$this->as3cf->get_storage_provider()->prepare_bucket_error( $buckets ),
|
||||
$this->error_args
|
||||
);
|
||||
|
||||
return $this->rest_ensure_response( 'get', static::name(), array() );
|
||||
}
|
||||
|
||||
$this->as3cf->notices->dismiss_notice( $this->error_args['custom_id'] );
|
||||
|
||||
return $this->rest_ensure_response( 'get', static::name(), array(
|
||||
'buckets' => $buckets,
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a REST POST request to create a bucket and returns saved status.
|
||||
*
|
||||
* NOTE: This does not update settings.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response|mixed
|
||||
*/
|
||||
public function post_buckets( WP_REST_Request $request ) {
|
||||
$data = $request->get_json_params();
|
||||
$bucket = empty( $data['bucket'] ) ? false : $data['bucket'];
|
||||
|
||||
// Front end validation should have caught this, it's all gone Pete Tong!
|
||||
if ( ! $bucket ) {
|
||||
$this->as3cf->notices->add_notice(
|
||||
__( 'No bucket name provided.', 'amazon-s3-and-cloudfront' ),
|
||||
$this->error_args
|
||||
);
|
||||
|
||||
return $this->rest_ensure_response( 'post', static::name(), array( 'saved' => false ) );
|
||||
}
|
||||
|
||||
$bucket = $this->as3cf->check_bucket( $bucket );
|
||||
|
||||
// Front end validation should have caught this, it's all gone Pete Tong!
|
||||
if ( ! $bucket ) {
|
||||
$this->as3cf->notices->add_notice(
|
||||
__( 'Bucket name not valid.', 'amazon-s3-and-cloudfront' ),
|
||||
$this->error_args
|
||||
);
|
||||
|
||||
return $this->rest_ensure_response( 'post', static::name(), array( 'saved' => false ) );
|
||||
}
|
||||
|
||||
$region = empty( $data['region'] ) ? false : $data['region'];
|
||||
|
||||
// Front end validation should have caught this, it's all gone Pete Tong!
|
||||
if ( ! $region ) {
|
||||
$this->as3cf->notices->add_notice(
|
||||
__( 'No region provided.', 'amazon-s3-and-cloudfront' ),
|
||||
$this->error_args
|
||||
);
|
||||
|
||||
return $this->rest_ensure_response( 'post', static::name(), array( 'saved' => false ) );
|
||||
}
|
||||
|
||||
// Make sure defines agree with given params.
|
||||
$defines = $this->as3cf->get_defined_settings();
|
||||
|
||||
if ( ! empty( $defines['bucket'] ) && $defines['bucket'] !== $bucket ) {
|
||||
$this->as3cf->notices->add_notice(
|
||||
__( 'Bucket name does not match defined value.', 'amazon-s3-and-cloudfront' ),
|
||||
$this->error_args
|
||||
);
|
||||
|
||||
return $this->rest_ensure_response( 'post', static::name(), array( 'saved' => false ) );
|
||||
}
|
||||
|
||||
if ( ! empty( $defines['region'] ) && $defines['region'] !== $region ) {
|
||||
$this->as3cf->notices->add_notice(
|
||||
__( 'Region does not match defined value.', 'amazon-s3-and-cloudfront' ),
|
||||
$this->error_args
|
||||
);
|
||||
|
||||
return $this->rest_ensure_response( 'post', static::name(), array( 'saved' => false ) );
|
||||
}
|
||||
|
||||
$result = $this->as3cf->create_bucket( $bucket, $region );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$this->as3cf->notices->add_notice(
|
||||
$this->as3cf->get_storage_provider()->prepare_bucket_error( $result ),
|
||||
$this->error_args
|
||||
);
|
||||
|
||||
return $this->rest_ensure_response( 'post', static::name(), array( 'saved' => false ) );
|
||||
}
|
||||
|
||||
$this->as3cf->notices->dismiss_notice( $this->error_args['custom_id'] );
|
||||
|
||||
return $this->rest_ensure_response( 'post', static::name(), array( 'saved' => $result ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a REST PUT request to update a bucket's properties and returns saved status.
|
||||
*
|
||||
* NOTE: This does not directly update settings.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response|mixed
|
||||
*/
|
||||
public function put_buckets( WP_REST_Request $request ) {
|
||||
$data = $request->get_json_params();
|
||||
$bucket = empty( $data['bucket'] ) ? false : $data['bucket'];
|
||||
|
||||
do_action( 'as3cf_pre_update_bucket' );
|
||||
|
||||
add_filter( 'as3cf_api_response_put_buckets', function ( $response ) {
|
||||
do_action( 'as3cf_post_update_bucket', $response['saved'] );
|
||||
|
||||
return $response;
|
||||
} );
|
||||
|
||||
// Front end validation should have caught this, it's all gone Pete Tong!
|
||||
if ( ! $bucket ) {
|
||||
$this->as3cf->notices->add_notice(
|
||||
__( 'No bucket name provided.', 'amazon-s3-and-cloudfront' ),
|
||||
$this->error_args
|
||||
);
|
||||
|
||||
return $this->rest_ensure_response( 'put', static::name(), array( 'saved' => false ) );
|
||||
}
|
||||
|
||||
$bucket = $this->as3cf->check_bucket( $bucket );
|
||||
|
||||
// Front end validation should have caught this, it's all gone Pete Tong!
|
||||
if ( ! $bucket ) {
|
||||
$this->as3cf->notices->add_notice(
|
||||
__( 'Bucket name not valid.', 'amazon-s3-and-cloudfront' ),
|
||||
$this->error_args
|
||||
);
|
||||
|
||||
return $this->rest_ensure_response( 'put', static::name(), array( 'saved' => false ) );
|
||||
}
|
||||
|
||||
/*
|
||||
* At present the only reason to call this endpoint is to change the
|
||||
* Block All Public Access or Object Ownership status of an S3 bucket.
|
||||
* As such, `blockPublicAccess` and `enforceObjectOwnership are required properties.
|
||||
*
|
||||
* In the future this endpoint may change to allow updating of
|
||||
* other bucket properties, and may therefore be altered to have
|
||||
* a differing set of required and optional properties depending
|
||||
* on storage provider, including region.
|
||||
*/
|
||||
|
||||
if ( ! isset( $data['blockPublicAccess'] ) ) {
|
||||
$this->as3cf->notices->add_notice(
|
||||
__( 'No Block All Public Access status provided.', 'amazon-s3-and-cloudfront' ),
|
||||
$this->error_args
|
||||
);
|
||||
|
||||
return $this->rest_ensure_response( 'put', static::name(), array( 'saved' => false ) );
|
||||
}
|
||||
|
||||
if ( ! isset( $data['objectOwnershipEnforced'] ) ) {
|
||||
$this->as3cf->notices->add_notice(
|
||||
__( 'No Enforce Object Ownership status provided.', 'amazon-s3-and-cloudfront' ),
|
||||
$this->error_args
|
||||
);
|
||||
|
||||
return $this->rest_ensure_response( 'put', static::name(), array( 'saved' => false ) );
|
||||
}
|
||||
|
||||
$block = ! empty( $data['blockPublicAccess'] );
|
||||
|
||||
$result = $this->block_public_access( $bucket, $block );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$this->as3cf->notices->add_notice(
|
||||
$this->as3cf->get_storage_provider()->prepare_bucket_error( $result, true ),
|
||||
$this->error_args
|
||||
);
|
||||
|
||||
return $this->rest_ensure_response( 'put', static::name(), array( 'saved' => false ) );
|
||||
}
|
||||
|
||||
$enforce = ! empty( $data['objectOwnershipEnforced'] );
|
||||
|
||||
$result = $this->enforce_object_ownership( $bucket, $enforce );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$this->as3cf->notices->add_notice(
|
||||
$this->as3cf->get_storage_provider()->prepare_bucket_error( $result, true ),
|
||||
$this->error_args
|
||||
);
|
||||
|
||||
return $this->rest_ensure_response( 'put', static::name(), array( 'saved' => false ) );
|
||||
}
|
||||
|
||||
$this->as3cf->notices->dismiss_notice( $this->error_args['custom_id'] );
|
||||
$this->as3cf->bucket_changed();
|
||||
|
||||
return $this->rest_ensure_response( 'put', static::name(), array( 'saved' => true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Block All Public Access status of given bucket.
|
||||
*
|
||||
* @param string $bucket
|
||||
* @param boolean $block
|
||||
*
|
||||
* @return WP_Error|bool
|
||||
*
|
||||
* There's no actual setting for this, the state of public access to the bucket is checked as required.
|
||||
*/
|
||||
public function block_public_access( string $bucket, bool $block ) {
|
||||
if ( false === $this->as3cf->get_storage_provider()->block_public_access_supported() ) {
|
||||
return new WP_Error(
|
||||
'exception',
|
||||
sprintf(
|
||||
_x(
|
||||
"Can't change Block All Public Access setting for %s buckets.",
|
||||
"Trying to change public access setting for given provider's bucket.",
|
||||
'amazon-s3-and-cloudfront'
|
||||
),
|
||||
$this->as3cf->get_storage_provider()->get_provider_service_name()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( $this->as3cf->get_storage_provider()->needs_access_keys() ) {
|
||||
return new WP_Error(
|
||||
'exception',
|
||||
__( 'Storage Provider not configured with access credentials.', 'amazon-s3-and-cloudfront' )
|
||||
);
|
||||
}
|
||||
|
||||
if ( empty( $bucket ) ) {
|
||||
return new WP_Error(
|
||||
'exception',
|
||||
__( 'No bucket name provided.', 'amazon-s3-and-cloudfront' )
|
||||
);
|
||||
}
|
||||
|
||||
// Make sure given bucket name matches currently set bucket.
|
||||
$settings_bucket = $this->as3cf->get_setting( 'bucket' );
|
||||
|
||||
if ( $settings_bucket !== $bucket ) {
|
||||
return new WP_Error(
|
||||
'exception',
|
||||
__( 'Bucket name does not match currently set bucket.', 'amazon-s3-and-cloudfront' )
|
||||
);
|
||||
}
|
||||
|
||||
$region = $this->as3cf->get_setting( 'region' );
|
||||
$block = ! empty( $block );
|
||||
|
||||
try {
|
||||
$public_access_blocked = $this->as3cf->get_provider_client( $region )->public_access_blocked( $bucket );
|
||||
} catch ( Exception $e ) {
|
||||
$public_access_blocked = null;
|
||||
AS3CF_Error::log( $e->getMessage() );
|
||||
}
|
||||
|
||||
if ( empty( $block ) !== empty( $public_access_blocked ) ) {
|
||||
try {
|
||||
$this->as3cf->get_provider_client( $region )->block_public_access( $bucket, $block );
|
||||
} catch ( Exception $e ) {
|
||||
AS3CF_Error::log( $e->getMessage() );
|
||||
|
||||
return new WP_Error(
|
||||
'exception',
|
||||
__( 'Could not change Block All Public Access status for bucket.', 'amazon-s3-and-cloudfront' )
|
||||
);
|
||||
}
|
||||
|
||||
// The bucket level request may succeed, but account level overrides may negate the change or the change simply silently failed.
|
||||
// So check that all is as expected as we can't change the account level settings.
|
||||
try {
|
||||
$public_access_blocked = $this->as3cf->get_provider_client( $region )->public_access_blocked( $bucket );
|
||||
} catch ( Exception $e ) {
|
||||
$public_access_blocked = null;
|
||||
AS3CF_Error::log( $e->getMessage() );
|
||||
}
|
||||
|
||||
if ( empty( $block ) !== empty( $public_access_blocked ) ) {
|
||||
if ( $block ) {
|
||||
$notice_message = __( '<strong>Failed to Enable Block All Public Access</strong> — We could not enable Block All Public Access. You will need to log in to the AWS Console and do it manually.', 'amazon-s3-and-cloudfront' );
|
||||
} else {
|
||||
$notice_message = __( '<strong>Failed to Disable Block All Public Access</strong> — We could not disable Block All Public Access. You will need to log in to the AWS Console and do it manually.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
$notice_message .= ' ' . $this->as3cf->settings_more_info_link( 'bucket' );
|
||||
|
||||
return new WP_Error( 'exception', $notice_message );
|
||||
}
|
||||
|
||||
// Successfully changed Block All Public Access status.
|
||||
return true;
|
||||
}
|
||||
|
||||
// All good, but nothing to do.
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update Object Ownership status of given bucket.
|
||||
*
|
||||
* @param string $bucket
|
||||
* @param boolean $enforce
|
||||
*
|
||||
* @return WP_Error|bool
|
||||
*
|
||||
* There's no actual setting for this, the state of object ownership controls in the bucket is checked as required.
|
||||
*/
|
||||
public function enforce_object_ownership( string $bucket, bool $enforce ) {
|
||||
if ( false === $this->as3cf->get_storage_provider()->object_ownership_supported() ) {
|
||||
return new WP_Error(
|
||||
'exception',
|
||||
sprintf(
|
||||
_x(
|
||||
"Can't change Object Ownership setting for %s buckets.",
|
||||
"Trying to change object ownership setting for given provider's bucket.",
|
||||
'amazon-s3-and-cloudfront'
|
||||
),
|
||||
$this->as3cf->get_storage_provider()->get_provider_service_name()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ( $this->as3cf->get_storage_provider()->needs_access_keys() ) {
|
||||
return new WP_Error(
|
||||
'exception',
|
||||
__( 'Storage Provider not configured with access credentials.', 'amazon-s3-and-cloudfront' )
|
||||
);
|
||||
}
|
||||
|
||||
if ( empty( $bucket ) ) {
|
||||
return new WP_Error(
|
||||
'exception',
|
||||
__( 'No bucket name provided.', 'amazon-s3-and-cloudfront' )
|
||||
);
|
||||
}
|
||||
|
||||
// Make sure given bucket name matches currently set bucket.
|
||||
$settings_bucket = $this->as3cf->get_setting( 'bucket' );
|
||||
|
||||
if ( $settings_bucket !== $bucket ) {
|
||||
return new WP_Error(
|
||||
'exception',
|
||||
__( 'Bucket name does not match currently set bucket.', 'amazon-s3-and-cloudfront' )
|
||||
);
|
||||
}
|
||||
|
||||
$region = $this->as3cf->get_setting( 'region' );
|
||||
$enforce = ! empty( $enforce );
|
||||
|
||||
try {
|
||||
$object_ownership_enforced = $this->as3cf->get_provider_client( $region )->object_ownership_enforced( $bucket );
|
||||
} catch ( Exception $e ) {
|
||||
$object_ownership_enforced = null;
|
||||
AS3CF_Error::log( $e->getMessage() );
|
||||
}
|
||||
|
||||
if ( empty( $enforce ) !== empty( $object_ownership_enforced ) ) {
|
||||
try {
|
||||
$this->as3cf->get_provider_client( $region )->enforce_object_ownership( $bucket, $enforce );
|
||||
} catch ( Exception $e ) {
|
||||
AS3CF_Error::log( $e->getMessage() );
|
||||
|
||||
return new WP_Error(
|
||||
'exception',
|
||||
__( 'Could not change Object Ownership status for bucket.', 'amazon-s3-and-cloudfront' )
|
||||
);
|
||||
}
|
||||
|
||||
// The bucket level request may succeed, but as it does not return a status from the API, we don't know for sure.
|
||||
try {
|
||||
$object_ownership_enforced = $this->as3cf->get_provider_client( $region )->object_ownership_enforced( $bucket );
|
||||
} catch ( Exception $e ) {
|
||||
$object_ownership_enforced = null;
|
||||
AS3CF_Error::log( $e->getMessage() );
|
||||
}
|
||||
|
||||
if ( empty( $enforce ) !== empty( $object_ownership_enforced ) ) {
|
||||
if ( $enforce ) {
|
||||
$notice_message = __( '<strong>Failed to Enforce Object Ownership</strong> — We could not enforce Object Ownership. You will need to log in to the AWS Console and do it manually.', 'amazon-s3-and-cloudfront' );
|
||||
} else {
|
||||
$notice_message = __( '<strong>Failed to turn off Object Ownership Enforcement</strong> — We could not turn off Object Ownership enforcement. You will need to log in to the AWS Console and do it manually.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
$notice_message .= ' ' . $this->as3cf->settings_more_info_link( 'bucket' );
|
||||
|
||||
return new WP_Error( 'exception', $notice_message );
|
||||
}
|
||||
|
||||
// Successfully changed Object Ownership status.
|
||||
return true;
|
||||
}
|
||||
|
||||
// All good, but nothing to do.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
42
classes/api/v1/diagnostics.php
Normal file
42
classes/api/v1/diagnostics.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\API\V1;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\API\API;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
class Diagnostics extends API {
|
||||
/** @var int */
|
||||
protected static $version = 1;
|
||||
|
||||
protected static $name = 'diagnostics';
|
||||
|
||||
/**
|
||||
* Register REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route(),
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'get_diagnostics' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a REST GET request and returns the current diagnostics.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response|mixed
|
||||
*/
|
||||
public function get_diagnostics( WP_REST_Request $request ) {
|
||||
return $this->rest_ensure_response( 'get', static::name(), array(
|
||||
'diagnostics' => $this->as3cf->output_diagnostic_info(),
|
||||
) );
|
||||
}
|
||||
}
|
||||
84
classes/api/v1/notifications.php
Normal file
84
classes/api/v1/notifications.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\API\V1;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\API\API;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
class Notifications extends API {
|
||||
/** @var int */
|
||||
protected static $version = 1;
|
||||
|
||||
/** @var string */
|
||||
protected static $name = 'notifications';
|
||||
|
||||
/**
|
||||
* Register REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route(),
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'get_notifications' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route(),
|
||||
array(
|
||||
'methods' => 'DELETE',
|
||||
'callback' => array( $this, 'delete_notifications' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a REST GET request and returns the current notifications.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response|mixed
|
||||
*/
|
||||
public function get_notifications( WP_REST_Request $request ) {
|
||||
$data = $request->get_query_params();
|
||||
$tab = empty( $data['tab'] ) ? '' : $data['tab'];
|
||||
$all_tabs = ! empty( $data['all_tabs'] );
|
||||
|
||||
return $this->rest_ensure_response( 'get', static::name(), array(
|
||||
'notifications' => $this->as3cf->get_notifications( $tab, $all_tabs ),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a REST DELETE request and returns the current notifications.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response|mixed
|
||||
*/
|
||||
public function delete_notifications( WP_REST_Request $request ) {
|
||||
$data = $request->get_json_params();
|
||||
|
||||
if ( empty( $data['id'] ) ) {
|
||||
return $this->rest_ensure_response( 'delete', static::name(),
|
||||
new WP_Error( 'missing-notification-id', __( 'Notification ID not supplied.', 'amazon-s3-and-cloudfront' ) )
|
||||
);
|
||||
}
|
||||
|
||||
$this->as3cf->dismiss_notification( $data['id'] );
|
||||
|
||||
$tab = empty( $data['tab'] ) ? '' : $data['tab'];
|
||||
$all_tabs = ! empty( $data['all_tabs'] );
|
||||
|
||||
return $this->rest_ensure_response( 'delete', static::name(), array(
|
||||
'notifications' => $this->as3cf->get_notifications( $tab, $all_tabs ),
|
||||
) );
|
||||
}
|
||||
}
|
||||
409
classes/api/v1/settings.php
Normal file
409
classes/api/v1/settings.php
Normal file
@@ -0,0 +1,409 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\API\V1;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\API\API;
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
class Settings extends API {
|
||||
/** @var int */
|
||||
protected static $version = 1;
|
||||
|
||||
/** @var string */
|
||||
protected static $name = 'settings';
|
||||
|
||||
/**
|
||||
* Register REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route(),
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'get_settings' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route(),
|
||||
array(
|
||||
'methods' => 'PUT',
|
||||
'callback' => array( $this, 'put_settings' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a REST GET request and returns the current settings and defined settings keys.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response|mixed
|
||||
*/
|
||||
public function get_settings( WP_REST_Request $request ) {
|
||||
return $this->rest_ensure_response( 'get', static::name(), $this->common_response() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Common response values for this API endpoint.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function common_response(): array {
|
||||
return array(
|
||||
'settings' => $this->as3cf->obfuscate_sensitive_settings( $this->as3cf->get_all_settings() ),
|
||||
'defined_settings' => array_keys( $this->as3cf->get_defined_settings() ),
|
||||
'storage_providers' => $this->as3cf->get_available_storage_provider_details(),
|
||||
'delivery_providers' => $this->as3cf->get_available_delivery_provider_details(),
|
||||
'is_plugin_setup' => $this->as3cf->is_plugin_setup(),
|
||||
'is_plugin_setup_with_credentials' => $this->as3cf->is_plugin_setup( true ),
|
||||
'needs_access_keys' => $this->as3cf->get_storage_provider()->needs_access_keys(),
|
||||
'bucket_writable' => $this->as3cf->bucket_writable(),
|
||||
'urls' => $this->as3cf->get_js_urls(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a REST PUT request to save supplied settings
|
||||
* and returns saved status, current settings, defined settings keys,
|
||||
* and various config data that may be altered by a change in settings.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response|mixed
|
||||
*/
|
||||
public function put_settings( WP_REST_Request $request ) {
|
||||
$saved = true;
|
||||
$data = $request->get_json_params();
|
||||
|
||||
try {
|
||||
$changed = $this->save_settings( $data );
|
||||
} catch ( Exception $e ) {
|
||||
$changed = false;
|
||||
}
|
||||
|
||||
if ( $changed === false ) {
|
||||
$saved = false;
|
||||
$changed = array();
|
||||
}
|
||||
|
||||
return $this->rest_ensure_response(
|
||||
'put',
|
||||
static::name(),
|
||||
array_merge(
|
||||
$this->common_response(),
|
||||
array(
|
||||
'saved' => $saved,
|
||||
'changed_settings' => $changed,
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle saving settings submitted by user.
|
||||
*
|
||||
* @param array $new_settings
|
||||
*
|
||||
* @return array|bool
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function save_settings( array $new_settings ) {
|
||||
$changed_keys = array();
|
||||
$warning_args = array(
|
||||
'type' => 'warning',
|
||||
'only_show_in_settings' => true,
|
||||
'only_show_on_tab' => 'media',
|
||||
);
|
||||
|
||||
do_action( 'as3cf_pre_save_settings' );
|
||||
|
||||
$allowed = $this->as3cf->get_allowed_settings_keys();
|
||||
$old_settings = $this->as3cf->get_all_settings( false );
|
||||
|
||||
// Merge in defined settings as they take precedence and must overwrite anything supplied.
|
||||
// Only needed to allow for validation, during save defined settings are removed from array anyway.
|
||||
$new_settings = array_merge( $new_settings, $this->as3cf->get_defined_settings() );
|
||||
|
||||
// Keep track of whether the delivery provider has changed and signed url settings need to be checked.
|
||||
$check_signed_urls_settings = $this->check_signed_urls_settings( $new_settings, $old_settings );
|
||||
|
||||
foreach ( $allowed as $key ) {
|
||||
// Special case for when Secret Access Key is not changed.
|
||||
if (
|
||||
'secret-access-key' === $key &&
|
||||
! empty( $new_settings['secret-access-key'] ) &&
|
||||
_x( '-- not shown --', 'placeholder for hidden secret access key, 39 char max', 'amazon-s3-and-cloudfront' ) === $new_settings['secret-access-key']
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Whether defined or not, get rid of old database setting for key.
|
||||
$this->as3cf->remove_setting( $key );
|
||||
|
||||
if ( ! isset( $new_settings[ $key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $this->as3cf->sanitize_setting( $key, $new_settings[ $key ] );
|
||||
|
||||
if ( 'key-file' === $key && is_string( $value ) && ! empty( $value ) ) {
|
||||
// Guard against empty JSON.
|
||||
if ( '""' === $value ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = json_decode( $value, true );
|
||||
|
||||
if ( empty( $value ) ) {
|
||||
return $this->return_with_error( __( 'Key File not valid JSON.', 'amazon-s3-and-cloudfront' ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'bucket' === $key && $this->setting_changed( $old_settings, $key, $value ) ) {
|
||||
$value = $this->as3cf->check_bucket( $value );
|
||||
|
||||
// Front end validation should have caught this, it's all gone Pete Tong!
|
||||
if ( ! $value ) {
|
||||
return $this->return_with_error( __( 'Bucket name not valid.', 'amazon-s3-and-cloudfront' ) );
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'signed-urls-key-file-path' === $key && is_string( $value ) && ! empty( $value ) ) {
|
||||
// Can be a Windows path with backslashes, so need to undo what PUT does to them.
|
||||
$value = stripslashes( $value );
|
||||
}
|
||||
|
||||
if ( $check_signed_urls_settings ) {
|
||||
if ( 'signed-urls-key-id' === $key && empty( $value ) ) {
|
||||
return $this->return_with_error( $this->as3cf->get_delivery_provider()->signed_urls_key_id_name() . _x( ' not provided.', 'missing form field', 'amazon-s3-and-cloudfront' ) );
|
||||
}
|
||||
|
||||
if ( 'signed-urls-key-file-path' === $key ) {
|
||||
if ( empty( $value ) ) {
|
||||
return $this->return_with_error( $this->as3cf->get_delivery_provider()->signed_urls_key_file_path_name() . _x( ' not provided.', 'missing form field', 'amazon-s3-and-cloudfront' ) );
|
||||
}
|
||||
|
||||
if ( ! $this->as3cf->get_delivery_provider()->validate_signed_urls_key_file_path( $value ) ) {
|
||||
// A notice is created by the validation function, we just want the rollback.
|
||||
return $this->return_with_error();
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'signed-urls-object-prefix' === $key && empty( $value ) ) {
|
||||
return $this->return_with_error( $this->as3cf->get_delivery_provider()->signed_urls_object_prefix_name() . _x( ' not provided.', 'missing form field', 'amazon-s3-and-cloudfront' ) );
|
||||
}
|
||||
}
|
||||
|
||||
$this->as3cf->set_setting( $key, $value );
|
||||
|
||||
if ( $this->setting_changed( $old_settings, $key, $value ) ) {
|
||||
$changed_keys[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
// Before checking that settings are all properly aligned,
|
||||
// ensure storage and delivery providers are in sync in core class.
|
||||
$this->as3cf->set_storage_provider();
|
||||
$this->as3cf->set_delivery_provider();
|
||||
|
||||
// If Storage Provider has changed, reset Delivery Provider to Storage if no longer compatible.
|
||||
// Also reset region name if no longer compatible.
|
||||
if ( ! empty( $changed_keys ) && in_array( 'provider', $changed_keys ) ) {
|
||||
$storage_provider = $this->as3cf->get_storage_provider();
|
||||
$storage_supported = $this->as3cf->get_delivery_provider()->supports_storage( $storage_provider->get_provider_key_name() );
|
||||
|
||||
if ( ! $storage_supported ) {
|
||||
$this->as3cf->set_setting( 'delivery-provider', $this->as3cf->get_default_delivery_provider() );
|
||||
}
|
||||
|
||||
$region = $this->as3cf->get_setting( 'region' );
|
||||
|
||||
if (
|
||||
( empty( $region ) && $storage_provider->region_required() ) ||
|
||||
( ! empty( $region ) && ! in_array( $region, array_keys( $storage_provider->get_regions() ) ) )
|
||||
) {
|
||||
// Note: We don't trigger changed keys when resetting the region as we do not want to stop
|
||||
// the storage provider change because the bucket is not usable.
|
||||
// The provider/region/bucket combination will be checked in due course.
|
||||
$this->as3cf->set_setting( 'region', $storage_provider->get_default_region() );
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure newly selected provider/region/bucket combination is usable and bucket's extra data is up-to-date.
|
||||
if ( ! empty( $changed_keys ) && ( in_array( 'bucket', $changed_keys ) || in_array( 'region', $changed_keys ) ) ) {
|
||||
$bucket_error = $this->check_set_bucket_for_error();
|
||||
|
||||
if ( ! empty( $bucket_error ) ) {
|
||||
return $this->return_with_error( $bucket_error );
|
||||
}
|
||||
|
||||
// Has a side effect of saving settings, but that should be ok if bucket changed and validated,
|
||||
// as changing bucket is usually a separate process from changing other settings.
|
||||
$this->as3cf->bucket_changed();
|
||||
}
|
||||
|
||||
// If delivery provider has been changed, but not intentionally, we need to warn the user.
|
||||
if (
|
||||
! empty( $changed_keys ) &&
|
||||
! in_array( 'delivery-provider', $changed_keys ) &&
|
||||
$this->setting_changed( $old_settings, 'delivery-provider', $this->as3cf->get_setting( 'delivery-provider' ) )
|
||||
) {
|
||||
$changed_keys = array_unique( array_merge( $changed_keys, array( 'delivery-provider' ) ) );
|
||||
$storage_provider = $this->as3cf->get_storage_provider();
|
||||
$warnings[] = sprintf( __( 'Delivery Provider has been reset to the default for %s', 'amazon-s3-and-cloudfront' ), $storage_provider->get_provider_service_name() );
|
||||
}
|
||||
|
||||
// None of the settings produced an error of their own.
|
||||
// However, side effects may re-instate the notice after this.
|
||||
$this->as3cf->notices->dismiss_notice( 'save-settings-error' );
|
||||
|
||||
// Check provider/region/bucket to see whether any error notice or warning necessary.
|
||||
// Note: As this is a side effect of another change such as switching provider, settings save is not prevented.
|
||||
if ( ! in_array( 'bucket', $changed_keys ) && ! in_array( 'region', $changed_keys ) && ! empty( $this->as3cf->get_setting( 'bucket' ) ) ) {
|
||||
$bucket_error = $this->check_set_bucket_for_error();
|
||||
|
||||
if ( empty( $bucket_error ) && $this->setting_changed( $old_settings, 'region', $this->as3cf->get_setting( 'region' ) ) ) {
|
||||
// If region has been changed, but not intentionally, we may need to warn user.
|
||||
$changed_keys = array_unique( array_merge( $changed_keys, array( 'region' ) ) );
|
||||
$region_name = $this->as3cf->get_storage_provider()->get_region_name( $this->as3cf->get_setting( 'region' ) );
|
||||
$warnings[] = sprintf( __( 'Region has been changed to %s', 'amazon-s3-and-cloudfront' ), $region_name );
|
||||
}
|
||||
}
|
||||
|
||||
// Great success ...
|
||||
$this->as3cf->save_settings();
|
||||
|
||||
// ... but there may be warnings.
|
||||
if ( ! empty( $warnings ) ) {
|
||||
foreach ( $warnings as $warning ) {
|
||||
$this->as3cf->notices->add_notice( $warning, $warning_args );
|
||||
}
|
||||
}
|
||||
|
||||
do_action( 'as3cf_post_save_settings', true );
|
||||
|
||||
return $changed_keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Has the given setting changed?
|
||||
*
|
||||
* @param array $old_settings
|
||||
* @param string $key
|
||||
* @param mixed $value
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function setting_changed( array $old_settings, string $key, $value ): bool {
|
||||
if (
|
||||
( empty( $old_settings[ $key ] ) !== empty( $value ) ) ||
|
||||
( isset( $old_settings[ $key ] ) && $old_settings[ $key ] !== $value )
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that provider/region/bucket combination is usable.
|
||||
*
|
||||
* @return string|WP_Error|null Error/string if problems, null otherwise.
|
||||
*/
|
||||
private function check_set_bucket_for_error() {
|
||||
$bucket = $this->as3cf->get_setting( 'bucket' );
|
||||
|
||||
if ( empty( $bucket ) ) {
|
||||
return __( 'No bucket provided.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
// If region not required as a parameter, and not already defined, get it from bucket.
|
||||
if ( $this->as3cf->get_storage_provider()->region_required() || $this->as3cf->get_defined_setting( 'region', false ) ) {
|
||||
$region = $this->as3cf->get_setting( 'region', false );
|
||||
} else {
|
||||
$region = $this->as3cf->get_bucket_region( $bucket, false );
|
||||
}
|
||||
|
||||
// When region is force checked and has an error, a notice is already created.
|
||||
if ( is_wp_error( $region ) ) {
|
||||
return $region;
|
||||
}
|
||||
|
||||
if ( ! $region ) {
|
||||
return __( 'No region provided.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
// Region may have been updated.
|
||||
$this->as3cf->set_setting( 'region', $region );
|
||||
|
||||
$can_write = $this->as3cf->check_write_permission( $bucket, $region );
|
||||
|
||||
if ( is_wp_error( $can_write ) ) {
|
||||
return $this->as3cf->get_storage_provider()->prepare_bucket_error( $can_write );
|
||||
}
|
||||
|
||||
if ( ! $can_write ) {
|
||||
return __( 'Access Denied to Bucket.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if we need to test signed URL settings and potentially prevent the save operation. We only need to test
|
||||
* this if:
|
||||
* - The delivery provider has not changed.
|
||||
* - And signed URLs are enabled in the new settings.
|
||||
* - And the delivery provider allows the use of signed URLs with a key file.
|
||||
*
|
||||
* @param array $new_settings
|
||||
* @param array $old_settings
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function check_signed_urls_settings( array $new_settings, array $old_settings ): bool {
|
||||
$delivery_provider_changed = ! empty( $new_settings['delivery-provider'] ) && $this->setting_changed( $old_settings, 'delivery-provider', $new_settings['delivery-provider'] );
|
||||
$signed_urls_enabled = ! empty( $new_settings['enable-delivery-domain'] ) && ! empty( $new_settings['enable-signed-urls'] );
|
||||
|
||||
return ! $delivery_provider_changed && $signed_urls_enabled ? $this->as3cf->get_delivery_provider()->use_signed_urls_key_file_allowed() : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Logs an error notice if string given, resets settings and then returns false.
|
||||
*
|
||||
* @param string|WP_Error|null $error Message for new notice, or WP_Error or null if new notice not needed.
|
||||
*
|
||||
* @return false
|
||||
* @throws Exception
|
||||
*/
|
||||
private function return_with_error( $error = null ): bool {
|
||||
if ( ! is_wp_error( $error ) && ! empty( $error ) ) {
|
||||
$this->as3cf->notices->add_notice(
|
||||
$error,
|
||||
array(
|
||||
'type' => 'error',
|
||||
'only_show_in_settings' => true,
|
||||
'only_show_on_tab' => 'media',
|
||||
'custom_id' => 'save-settings-error',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Revert all changes.
|
||||
$this->as3cf->get_settings( true );
|
||||
$this->as3cf->set_storage_provider();
|
||||
$this->as3cf->set_delivery_provider();
|
||||
|
||||
do_action( 'as3cf_post_save_settings', false );
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
66
classes/api/v1/state.php
Normal file
66
classes/api/v1/state.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\API\V1;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\API\API;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
class State extends API {
|
||||
/** @var int */
|
||||
protected static $version = 1;
|
||||
|
||||
/** @var string */
|
||||
protected static $name = 'state';
|
||||
|
||||
/**
|
||||
* Register REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route(),
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'get_state' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a REST GET request and returns the current state.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response|mixed
|
||||
*/
|
||||
public function get_state( WP_REST_Request $request ) {
|
||||
$params = $request->get_params();
|
||||
|
||||
$skip_transient = false;
|
||||
$force = false;
|
||||
$forced_blog_id = 0;
|
||||
|
||||
if ( ! empty( $params['refreshMediaCounts'] ) ) {
|
||||
$skip_transient = true;
|
||||
$force = true;
|
||||
$forced_blog_id = -1;
|
||||
}
|
||||
|
||||
return $this->rest_ensure_response(
|
||||
'get',
|
||||
static::name(),
|
||||
array_merge(
|
||||
$this->endpoint_common_response( Settings::name() ),
|
||||
array(
|
||||
'counts' => $this->as3cf->media_counts( $skip_transient, $force, $forced_blog_id ),
|
||||
'summary_counts' => $this->as3cf->get_summary_counts(),
|
||||
'offload_remaining_upsell' => $this->as3cf->get_offload_remaining_upsell_message(),
|
||||
'upgrades' => $this->as3cf->get_upgrades_info(),
|
||||
'settings_validation' => $this->as3cf->settings_validation_status(),
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
74
classes/api/v1/url-preview.php
Normal file
74
classes/api/v1/url-preview.php
Normal file
@@ -0,0 +1,74 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\API\V1;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\API\API;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
class URL_Preview extends API {
|
||||
/** @var int */
|
||||
protected static $version = 1;
|
||||
|
||||
protected static $name = 'url-preview';
|
||||
|
||||
/**
|
||||
* Register REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route(),
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'get_url_preview' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route(),
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( $this, 'post_url_preview' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a REST GET request and returns the current url_preview.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response|mixed
|
||||
*/
|
||||
public function get_url_preview( WP_REST_Request $request ) {
|
||||
return $this->rest_ensure_response( 'get', static::name(), array(
|
||||
'url_example' => $this->as3cf->get_url_preview(),
|
||||
'url_parts' => $this->as3cf->get_url_preview( true ),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a REST POST request and returns the url_preview for given settings.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response|mixed
|
||||
*/
|
||||
public function post_url_preview( WP_REST_Request $request ) {
|
||||
$data = $request->get_json_params();
|
||||
|
||||
// If no settings provided, don't provide any urls to signify a soft error.
|
||||
if ( empty( $data['settings'] ) || ! is_array( $data['settings'] ) ) {
|
||||
return $this->rest_ensure_response( 'post', static::name(), array() );
|
||||
}
|
||||
|
||||
return $this->rest_ensure_response( 'post', static::name(), array(
|
||||
'url_example' => $this->as3cf->get_url_preview( false, null, $data['settings'] ),
|
||||
'url_parts' => $this->as3cf->get_url_preview( true, null, $data['settings'] ),
|
||||
) );
|
||||
}
|
||||
}
|
||||
674
classes/as3cf-compatibility-check.php
Normal file
674
classes/as3cf-compatibility-check.php
Normal file
@@ -0,0 +1,674 @@
|
||||
<?php
|
||||
/**
|
||||
* AS3CF Compatibility Check
|
||||
*
|
||||
* @package wp-aws
|
||||
* @copyright Copyright (c) 2015, Delicious Brains
|
||||
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
|
||||
* @since 0.1
|
||||
*/
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Check if already defined
|
||||
if ( ! class_exists( 'AS3CF_Compatibility_Check' ) ) {
|
||||
|
||||
/**
|
||||
* AS3CF_Compatibility_Check Class
|
||||
*
|
||||
* This class handles compatibility between an AWS plugin and a required parent plugin
|
||||
*
|
||||
* @since 0.1
|
||||
*/
|
||||
class AS3CF_Compatibility_Check {
|
||||
|
||||
/**
|
||||
* @var string The derived key of the plugin from the name, e.g. amazon-s3-and-cloudfront
|
||||
*/
|
||||
protected $plugin_slug;
|
||||
|
||||
/**
|
||||
* @var string The name of the plugin, e.g. WP Offload Media
|
||||
*/
|
||||
protected $plugin_name;
|
||||
|
||||
/**
|
||||
* @var string The file path to the plugin's main file
|
||||
*/
|
||||
protected $plugin_file_path;
|
||||
|
||||
/**
|
||||
* @var null|string The name of the required parent plugin
|
||||
*/
|
||||
protected $parent_plugin_name;
|
||||
|
||||
/**
|
||||
* @var null|string The key of the required parent plugin, e.g. amazon-web-services
|
||||
*/
|
||||
protected $parent_plugin_slug;
|
||||
|
||||
/**
|
||||
* @var null|string The required version of the parent plugin
|
||||
*/
|
||||
protected $parent_plugin_required_version;
|
||||
|
||||
/**
|
||||
* @var null|string Optional name of the required parent plugin's filename if different to {$parent_plugin}.php
|
||||
*/
|
||||
protected $parent_plugin_filename;
|
||||
|
||||
/**
|
||||
* @var bool Do we deactivate the plugin if the requirements are not met?
|
||||
*/
|
||||
protected $deactivate_if_not_compatible;
|
||||
|
||||
/**
|
||||
* @var string The URL of the plugin if not on the WordPress.org repo
|
||||
*/
|
||||
protected $parent_plugin_url;
|
||||
|
||||
/**
|
||||
* @var string The error message to display in the admin notice
|
||||
*/
|
||||
protected $error_message;
|
||||
|
||||
/**
|
||||
* @var string The CSS class for the notice
|
||||
*/
|
||||
protected $notice_class = 'error';
|
||||
|
||||
/**
|
||||
* @var bool Used to store if we are installing or updating plugins once per page request
|
||||
*/
|
||||
protected static $is_installing_or_updating_plugins;
|
||||
|
||||
/**
|
||||
* @param string $plugin_name
|
||||
* @param string $plugin_slug
|
||||
* @param string $plugin_file_path
|
||||
* @param string|null $parent_plugin_name
|
||||
* @param string|null $parent_plugin_slug
|
||||
* @param string|null $parent_plugin_required_version
|
||||
* @param string|null $parent_plugin_filename
|
||||
* @param bool|false $deactivate_if_not_compatible
|
||||
* @param string|null $parent_plugin_url
|
||||
*/
|
||||
function __construct( $plugin_name, $plugin_slug, $plugin_file_path, $parent_plugin_name = null, $parent_plugin_slug = null, $parent_plugin_required_version = null, $parent_plugin_filename = null, $deactivate_if_not_compatible = false, $parent_plugin_url = null ) {
|
||||
$this->plugin_name = $plugin_name;
|
||||
$this->plugin_slug = $plugin_slug;
|
||||
$this->plugin_file_path = $plugin_file_path;
|
||||
$this->parent_plugin_name = $parent_plugin_name;
|
||||
$this->parent_plugin_slug = $parent_plugin_slug;
|
||||
$this->parent_plugin_required_version = $parent_plugin_required_version;
|
||||
$this->parent_plugin_filename = $parent_plugin_filename;
|
||||
$this->deactivate_if_not_compatible = $deactivate_if_not_compatible;
|
||||
$this->parent_plugin_url = $parent_plugin_url;
|
||||
|
||||
add_action( 'admin_notices', array( $this, 'hook_admin_notices' ) );
|
||||
add_action( 'network_admin_notices', array( $this, 'hook_admin_notices' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the plugin compatible?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function is_compatible() {
|
||||
$compatible = $this->get_error_msg() ? false : true;
|
||||
|
||||
$GLOBALS['aws_meta'][ $this->plugin_slug ]['compatible'] = $compatible;
|
||||
|
||||
return $compatible;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is a plugin active
|
||||
*
|
||||
* @param string $plugin_base
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function is_plugin_active( $plugin_base ) {
|
||||
include_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
return is_plugin_active( $plugin_base );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the basename for the plugin
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_plugin_basename() {
|
||||
return plugin_basename( $this->plugin_file_path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the parent plugin
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_parent_plugin_name() {
|
||||
if ( ! is_null( $this->parent_plugin_name ) ) {
|
||||
return $this->parent_plugin_name;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the class of the parent plugin
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_parent_plugin_class() {
|
||||
if ( ! is_null( $this->parent_plugin_slug ) ) {
|
||||
$class = ucwords( str_replace( '-', ' ', $this->parent_plugin_slug ) );
|
||||
|
||||
return str_replace( ' ', '_', $class );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filename of the main parent plugin file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_parent_plugin_filename() {
|
||||
if ( ! is_null( $this->parent_plugin_slug ) ) {
|
||||
$filename = $this->parent_plugin_slug;
|
||||
if ( ! is_null( $this->parent_plugin_filename ) ) {
|
||||
$filename = basename( $this->parent_plugin_filename, '.php' );
|
||||
}
|
||||
|
||||
return $filename . '.php';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the basename of the parent plugin {slug}/{slug}.php
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_parent_plugin_basename() {
|
||||
if ( ! is_null( $this->parent_plugin_slug ) ) {
|
||||
$file_name = $this->get_parent_plugin_filename();
|
||||
|
||||
return $this->parent_plugin_slug . '/' . $file_name;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL for the parent plugin. Defaults to a wordpress.org URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_parent_plugin_url() {
|
||||
if ( ! is_null( $this->parent_plugin_slug ) ) {
|
||||
$url = 'https://wordpress.org/extend/plugins/' . $this->parent_plugin_slug . '/';
|
||||
if ( ! is_null( $this->parent_plugin_url ) ) {
|
||||
$url = $this->parent_plugin_url;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a URL to perform core actions on for a plugin
|
||||
*
|
||||
* @param string $action Such as activate, deactivate, install, upgrade
|
||||
* @param string|null $basename
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_plugin_action_url( $action, $basename = null ) {
|
||||
if ( is_null( $basename ) ) {
|
||||
$basename = $this->get_plugin_basename();
|
||||
}
|
||||
|
||||
$nonce_action = $action . '-plugin_' . $basename;
|
||||
$page = 'plugins';
|
||||
|
||||
if ( in_array( $action, array( 'upgrade', 'install' ) ) ) {
|
||||
$page = 'update';
|
||||
$action .= '-plugin';
|
||||
}
|
||||
|
||||
$url = wp_nonce_url( network_admin_url( $page . '.php?action=' . $action . '&plugin=' . $basename ), $nonce_action );
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the error message to be returned for the admin notice
|
||||
*
|
||||
* @param string $message
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function set_error_msg( $message ) {
|
||||
// Replace the space between the last two words with to prevent typographic widows
|
||||
$message = preg_replace( '/\s([\w]+[.,!\:;\\"-?]{0,1})$/', ' \\1', $message, 1 );
|
||||
|
||||
$this->error_message = $message;
|
||||
|
||||
return $this->error_message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the parent plugin is active and enabled, ie. not disabled due to
|
||||
* compatibility issues up the chain.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function is_parent_plugin_enabled() {
|
||||
$class = $this->get_parent_plugin_class();
|
||||
if ( ! class_exists( $class ) ) {
|
||||
// Class not even loaded
|
||||
return false;
|
||||
}
|
||||
|
||||
// call_user_func overcomes parse errors on PHP versions < 5.3
|
||||
if ( method_exists( $class, 'is_compatible' ) && ! call_user_func( array( $class, 'is_compatible' ) ) ) {
|
||||
// The plugin is active but not compatible
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the parent plugin is at a specific version
|
||||
*
|
||||
* @param string $version
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function is_parent_plugin_at_version( $version ) {
|
||||
$current_parent_plugin_version = isset( $GLOBALS['aws_meta'][ $this->parent_plugin_slug ]['version'] ) ? $GLOBALS['aws_meta'][ $this->parent_plugin_slug ]['version'] : 0;
|
||||
|
||||
return version_compare( $current_parent_plugin_version, $version, '>=' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the compatibility error message
|
||||
*
|
||||
* @return string|bool
|
||||
*/
|
||||
public function get_error_msg() {
|
||||
if ( ! is_null( $this->error_message ) ) {
|
||||
return $this->error_message;
|
||||
}
|
||||
|
||||
$plugin_basename = $this->get_plugin_basename();
|
||||
$deactivate_url = $this->get_plugin_action_url( 'deactivate', $plugin_basename );
|
||||
$deactivate_link = sprintf( '<a style="text-decoration:none;" href="%s">%s</a>', $deactivate_url, __( 'deactivate' ) );
|
||||
$hide_notice_msg = '<br><em>' . sprintf( __( 'You can %s the %s plugin to get rid of this notice.' ), $deactivate_link, $this->plugin_name ) . '</em>';
|
||||
|
||||
// Check basic requirements for AWS SDK.
|
||||
$sdk_errors = $this->get_sdk_requirements_errors();
|
||||
if ( ! empty( $sdk_errors ) ) {
|
||||
$sdk_errors = $this->get_sdk_error_msg() . $hide_notice_msg;
|
||||
|
||||
return $this->set_error_msg( $sdk_errors );
|
||||
}
|
||||
|
||||
// Remainder of tests are for addons.
|
||||
if ( is_null( $this->parent_plugin_slug ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$parent_basename = $this->get_parent_plugin_basename();
|
||||
$parent_plugin_link_html = sprintf( '<a style="text-decoration:none;" href="%s">%s</a>', $this->get_parent_plugin_url(), $this->get_parent_plugin_name() );
|
||||
|
||||
if ( ! $this->is_parent_plugin_enabled() ) {
|
||||
$msg = sprintf( __( '%s has been disabled as it requires the %s plugin.' ), $this->plugin_name, $parent_plugin_link_html );
|
||||
|
||||
if ( file_exists( WP_PLUGIN_DIR . '/' . $parent_basename ) ) {
|
||||
if ( isset( $GLOBALS['aws_meta'][ $this->parent_plugin_slug ]['compatible'] ) && ! $GLOBALS['aws_meta'][ $this->parent_plugin_slug ]['compatible'] ) {
|
||||
$msg = rtrim( $msg, '.' ) . ', ' . __( 'which is currently disabled.' );
|
||||
} else {
|
||||
$msg .= ' ' . __( 'It appears to be installed already.' );
|
||||
$activate_url = $this->get_plugin_action_url( 'activate', $parent_basename );
|
||||
$msg .= ' <a id="' . $this->plugin_slug . '-activate-parent" style="font-weight:bold;text-decoration:none;" href="' . $activate_url . '">' . _x( 'Activate it now.', 'Activate plugin' ) . '</a>';
|
||||
}
|
||||
} else {
|
||||
$install_url = 'https://deliciousbrains.com/my-account/';
|
||||
if ( is_null( $this->parent_plugin_url ) ) {
|
||||
$install_url = $this->get_plugin_action_url( 'install', $this->parent_plugin_slug );
|
||||
}
|
||||
$msg .= ' ' . sprintf( __( '<a href="%s">Install</a> and activate it.' ), $install_url );
|
||||
}
|
||||
|
||||
$msg .= $hide_notice_msg;
|
||||
|
||||
return $this->set_error_msg( $msg );
|
||||
}
|
||||
|
||||
$current_parent_plugin_version = isset( $GLOBALS['aws_meta'][ $this->parent_plugin_slug ]['version'] ) ? $GLOBALS['aws_meta'][ $this->parent_plugin_slug ]['version'] : 0;
|
||||
|
||||
if ( ! version_compare( $current_parent_plugin_version, $this->parent_plugin_required_version, '>=' ) ) {
|
||||
$msg = sprintf( __( '%s has been disabled as it requires version %s or later of the %s plugin.' ), $this->plugin_name, $this->parent_plugin_required_version, $parent_plugin_link_html );
|
||||
|
||||
if ( $current_parent_plugin_version ) {
|
||||
$msg .= ' ' . sprintf( __( 'You currently have version %s installed.' ), $current_parent_plugin_version );
|
||||
}
|
||||
|
||||
global $as3cfpro;
|
||||
|
||||
$update_url = $this->get_plugin_action_url( 'upgrade', $parent_basename );
|
||||
|
||||
$msg .= ' <a style="font-weight:bold;text-decoration:none;white-space:nowrap;" href="' . $update_url . '">' . __( 'Update to the latest version' ) . '</a>';
|
||||
$msg .= $hide_notice_msg;
|
||||
|
||||
return $this->set_error_msg( $msg );
|
||||
}
|
||||
|
||||
if ( ! isset( $GLOBALS['aws_meta'][ $this->parent_plugin_slug ]['supported_addon_versions'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! isset( $GLOBALS['aws_meta'][ $this->parent_plugin_slug ]['supported_addon_versions'][ $this->plugin_slug ] ) ) {
|
||||
$msg = sprintf( __( '%1$s has been disabled because it is not a supported addon of the %2$s plugin.' ), $this->plugin_name, $this->get_parent_plugin_name() );
|
||||
|
||||
return $this->set_error_msg( $msg );
|
||||
}
|
||||
|
||||
$this_plugin_version_required = $GLOBALS['aws_meta'][ $this->parent_plugin_slug ]['supported_addon_versions'][ $this->plugin_slug ];
|
||||
$this_plugin_version = $GLOBALS['aws_meta'][ $this->plugin_slug ]['version'];
|
||||
|
||||
if ( ! version_compare( $this_plugin_version, $this_plugin_version_required, '>=' ) ) {
|
||||
$msg = sprintf( __( '%1$s has been disabled because it will not work with the version of the %2$s plugin installed. %1$s %3$s or later is required.' ), $this->plugin_name, $this->get_parent_plugin_name(), $this_plugin_version_required );
|
||||
|
||||
$update_url = $this->get_plugin_action_url( 'upgrade', $plugin_basename );
|
||||
$upgrade_msg = ' <a style="font-weight:bold;text-decoration:none;white-space:nowrap;" href="' . $update_url . '">' . sprintf( __( 'Update %s to the latest version' ), $this->plugin_name ) . '</a>';
|
||||
|
||||
global $as3cfpro;
|
||||
|
||||
$msg .= $upgrade_msg;
|
||||
$msg .= $hide_notice_msg;
|
||||
|
||||
return $this->set_error_msg( $msg );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check plugin capabilities for a user
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function check_capabilities() {
|
||||
if ( is_multisite() ) {
|
||||
if ( ! current_user_can( 'manage_network_plugins' ) ) {
|
||||
return false; // Don't allow if the user can't manage network plugins
|
||||
}
|
||||
} else {
|
||||
// Don't allow if user doesn't have plugin management privileges
|
||||
$caps = array( 'activate_plugins', 'update_plugins', 'install_plugins' );
|
||||
foreach ( $caps as $cap ) {
|
||||
if ( ! current_user_can( $cap ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display compatibility notices to users who can manage plugins
|
||||
*/
|
||||
function hook_admin_notices() {
|
||||
if ( ! $this->check_capabilities() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( self::is_installing_or_updating_plugins() ) {
|
||||
// Don't show notice when installing or updating plugins
|
||||
return;
|
||||
}
|
||||
|
||||
$this->get_admin_notice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the admin notice to be displayed
|
||||
*/
|
||||
function get_admin_notice() {
|
||||
$error_msg = $this->get_error_msg();
|
||||
|
||||
if ( false === $error_msg || '' === $error_msg ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->deactivate_if_not_compatible ) {
|
||||
$deactivated_msg = sprintf( __( 'The %s plugin has been deactivated.' ), $this->plugin_name );
|
||||
|
||||
$error_msg = $deactivated_msg . ' ' . $error_msg;
|
||||
$this->render_notice( $error_msg );
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
deactivate_plugins( $this->plugin_file_path );
|
||||
} else {
|
||||
$this->render_notice( $error_msg );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the notice HTML
|
||||
*
|
||||
* @param string $message
|
||||
*/
|
||||
function render_notice( $message ) {
|
||||
printf( '<div id="as3cf-compat-notice' . $this->plugin_slug . '" class="' . $this->notice_class . ' as3cf-compatibility-notice"><p>%s</p></div>', $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the current process an install or upgrade of plugin(s)
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_installing_or_updating_plugins() {
|
||||
if ( ! is_null( self::$is_installing_or_updating_plugins ) ) {
|
||||
return self::$is_installing_or_updating_plugins;
|
||||
}
|
||||
|
||||
self::$is_installing_or_updating_plugins = false;
|
||||
|
||||
global $pagenow;
|
||||
|
||||
if ( 'update.php' === $pagenow && isset( $_GET['action'] ) && 'install-plugin' === $_GET['action'] ) {
|
||||
// We are installing a plugin
|
||||
self::$is_installing_or_updating_plugins = true;
|
||||
}
|
||||
|
||||
if ( 'plugins.php' === $pagenow && isset( $_POST['action'] ) ) {
|
||||
$action = $_POST['action'];
|
||||
if ( isset( $_POST['action2'] ) && '-1' !== $_POST['action2'] ) {
|
||||
$action = $_POST['action2'];
|
||||
}
|
||||
|
||||
if ( 'update-selected' === $action ) {
|
||||
// We are updating plugins from the plugin page
|
||||
self::$is_installing_or_updating_plugins = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ( 'update-core.php' === $pagenow && isset( $_GET['action'] ) && 'do-plugin-upgrade' === $_GET['action'] ) {
|
||||
// We are updating plugins from the updates page
|
||||
self::$is_installing_or_updating_plugins = true;
|
||||
}
|
||||
|
||||
return self::$is_installing_or_updating_plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if another version of WP Offload Media (Lite) is active and deactivates it.
|
||||
* To be hooked on `activated_plugin` so other plugin is deactivated when current plugin is activated.
|
||||
*
|
||||
* @param string $plugin
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function deactivate_other_instances( $plugin ) {
|
||||
if ( ! in_array( basename( $plugin ), array( 'amazon-s3-and-cloudfront-pro.php', 'wordpress-s3.php' ) ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$plugin_to_deactivate = 'wordpress-s3.php';
|
||||
$deactivated_notice_id = '1';
|
||||
$activated_plugin_min_version = '1.1';
|
||||
$plugin_to_deactivate_min_version = '1.0';
|
||||
if ( basename( $plugin ) === $plugin_to_deactivate ) {
|
||||
$plugin_to_deactivate = 'amazon-s3-and-cloudfront-pro.php';
|
||||
$deactivated_notice_id = '2';
|
||||
$activated_plugin_min_version = '1.0';
|
||||
$plugin_to_deactivate_min_version = '1.1';
|
||||
}
|
||||
|
||||
$version = self::get_plugin_version_from_basename( $plugin );
|
||||
|
||||
if ( version_compare( $version, $activated_plugin_min_version, '<' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( is_multisite() ) {
|
||||
$active_plugins = (array) get_site_option( 'active_sitewide_plugins', array() );
|
||||
$active_plugins = array_keys( $active_plugins );
|
||||
} else {
|
||||
$active_plugins = (array) get_option( 'active_plugins', array() );
|
||||
}
|
||||
|
||||
foreach ( $active_plugins as $basename ) {
|
||||
if ( false !== strpos( $basename, $plugin_to_deactivate ) ) {
|
||||
$version = self::get_plugin_version_from_basename( $basename );
|
||||
|
||||
if ( version_compare( $version, $plugin_to_deactivate_min_version, '<' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
set_transient( 'as3cf_deactivated_notice_id', $deactivated_notice_id, HOUR_IN_SECONDS );
|
||||
deactivate_plugins( $basename );
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get plugin data from basename
|
||||
*
|
||||
* @param string $basename
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_plugin_version_from_basename( $basename ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
$plugin_path = WP_PLUGIN_DIR . '/' . $basename;
|
||||
$plugin_data = get_plugin_data( $plugin_path );
|
||||
|
||||
return $plugin_data['Version'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of issues with the server's compatibility with the AWS SDK
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sdk_requirements_errors() {
|
||||
static $errors;
|
||||
|
||||
if ( ! is_null( $errors ) ) {
|
||||
return $errors;
|
||||
}
|
||||
|
||||
$errors = array();
|
||||
|
||||
if ( version_compare( PHP_VERSION, '5.5', '<' ) ) {
|
||||
$errors[] = __( 'a PHP version less than 5.5', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'SimpleXMLElement' ) ) {
|
||||
$errors[] = __( 'no SimpleXML PHP module', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'XMLWriter' ) ) {
|
||||
$errors[] = __( 'no XMLWriter PHP module', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'curl_version' ) ) {
|
||||
$errors[] = __( 'no PHP cURL library activated', 'amazon-s3-and-cloudfront' );
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
if ( ! ( $curl = curl_version() ) || empty( $curl['version'] ) || empty( $curl['features'] ) || version_compare( $curl['version'], '7.16.2', '<' ) ) {
|
||||
$errors[] = __( 'a cURL version less than 7.16.2', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
if ( ! empty( $curl['features'] ) ) {
|
||||
$curl_errors = array();
|
||||
|
||||
if ( ! CURL_VERSION_SSL ) {
|
||||
$curl_errors[] = 'OpenSSL';
|
||||
}
|
||||
|
||||
if ( ! CURL_VERSION_LIBZ ) {
|
||||
$curl_errors[] = 'zlib';
|
||||
}
|
||||
|
||||
if ( $curl_errors ) {
|
||||
$errors[] = __( 'cURL compiled without', 'amazon-s3-and-cloudfront' ) . ' ' . implode( ' or ', $curl_errors ); // xss ok
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! function_exists( 'curl_multi_exec' ) ) {
|
||||
$errors[] = __( 'the function curl_multi_exec disabled', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare an error message with compatibility issues
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_sdk_error_msg() {
|
||||
$errors = $this->get_sdk_requirements_errors();
|
||||
|
||||
if ( ! $errors ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$msg = __( 'The official Amazon Web Services SDK requires PHP 5.5+ with SimpleXML and XMLWriter modules, and cURL 7.16.2+ compiled with OpenSSL and zlib. Your server currently has', 'amazon-s3-and-cloudfront' );
|
||||
|
||||
if ( count( $errors ) > 1 ) {
|
||||
$last_one = ' and ' . array_pop( $errors );
|
||||
} else {
|
||||
$last_one = '';
|
||||
}
|
||||
|
||||
$msg .= ' ' . implode( ', ', $errors ) . $last_one . '.';
|
||||
|
||||
return $msg;
|
||||
}
|
||||
}
|
||||
}
|
||||
48
classes/as3cf-error.php
Normal file
48
classes/as3cf-error.php
Normal file
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* Error
|
||||
*
|
||||
* @package amazon-s3-and-cloudfront
|
||||
* @subpackage Classes/Error
|
||||
* @copyright Copyright (c) 2015, Delicious Brains
|
||||
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
|
||||
* @since 0.9.12
|
||||
*/
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* AS3CF_Error Class
|
||||
*
|
||||
* This class handles error logging
|
||||
*
|
||||
* @since 0.9.12
|
||||
*/
|
||||
class AS3CF_Error {
|
||||
|
||||
/**
|
||||
* Wrapper for error logging a message with plugin prefix
|
||||
*
|
||||
* phpcs:disable WordPress.PHP.DevelopmentFunctions
|
||||
*
|
||||
* @param mixed $message
|
||||
* @param string $plugin_prefix
|
||||
*/
|
||||
public static function log( $message, $plugin_prefix = '' ) {
|
||||
$prefix = 'AS3CF';
|
||||
if ( '' !== $plugin_prefix ) {
|
||||
$prefix .= '_' . $plugin_prefix;
|
||||
}
|
||||
|
||||
$prefix .= ': ';
|
||||
|
||||
if ( is_array( $message ) || is_object( $message ) ) {
|
||||
error_log( $prefix . print_r( $message, true ) );
|
||||
} else {
|
||||
error_log( $prefix . $message );
|
||||
}
|
||||
}
|
||||
}
|
||||
1084
classes/as3cf-filter.php
Normal file
1084
classes/as3cf-filter.php
Normal file
File diff suppressed because it is too large
Load Diff
604
classes/as3cf-notices.php
Normal file
604
classes/as3cf-notices.php
Normal file
@@ -0,0 +1,604 @@
|
||||
<?php
|
||||
|
||||
class AS3CF_Notices {
|
||||
|
||||
/**
|
||||
* @var AS3CF_Notices
|
||||
*/
|
||||
protected static $instance = null;
|
||||
|
||||
/**
|
||||
* @var Amazon_S3_And_CloudFront
|
||||
*/
|
||||
private $as3cf;
|
||||
|
||||
/**
|
||||
* Make this class a singleton
|
||||
*
|
||||
* Use this instead of __construct()
|
||||
*
|
||||
* @param Amazon_S3_And_CloudFront $as3cf
|
||||
*
|
||||
* @return AS3CF_Notices
|
||||
*/
|
||||
public static function get_instance( $as3cf ) {
|
||||
if ( ! isset( static::$instance ) ) {
|
||||
static::$instance = new static( $as3cf );
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param Amazon_S3_And_CloudFront $as3cf
|
||||
*/
|
||||
protected function __construct( $as3cf ) {
|
||||
$this->as3cf = $as3cf;
|
||||
|
||||
add_action( 'admin_notices', array( $this, 'admin_notices' ) );
|
||||
add_action( 'network_admin_notices', array( $this, 'admin_notices' ) );
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_notice_scripts' ) );
|
||||
add_action( 'wp_ajax_as3cf-dismiss-notice', array( $this, 'ajax_dismiss_notice' ) );
|
||||
|
||||
add_filter( $this->as3cf->get_plugin_prefix() . '_api_response', array( $this, 'api_response' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* As this class is a singleton it should not be clone-able
|
||||
*/
|
||||
protected function __clone() {
|
||||
// Singleton
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a notice.
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $args
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function build_notice( $message, $args = array() ) {
|
||||
$defaults = array(
|
||||
'type' => 'notice-info',
|
||||
'dismissible' => true,
|
||||
'inline' => false,
|
||||
'flash' => true,
|
||||
'only_show_to_user' => true, // The user who has initiated an action resulting in notice. Otherwise show to all users.
|
||||
'user_capabilities' => array( 'as3cf_compat_check', 'check_capabilities' ), // A user with these capabilities can see the notice. Can be a callback with the first array item the name of global class instance.
|
||||
'only_show_in_settings' => false,
|
||||
'only_show_on_tab' => false, // Only show on a specific WP Offload Media tab.
|
||||
'hide_on_parent' => false, // Hide the notice on a tab parent page (where an embedded notice is shown instead).
|
||||
'custom_id' => '',
|
||||
'auto_p' => true, // Automatically wrap the message in a <p>
|
||||
'class' => '', // Extra classes for the notice
|
||||
'show_callback' => false, // Callback to display extra info on notices. Passing a callback automatically handles show/hide toggle.
|
||||
'callback_args' => array(), // Arguments to pass to the callback.
|
||||
'lock_key' => '', // If lock key set, do not show message until lock released.
|
||||
'pre_render_callback' => false, // Callback to call before notice render.
|
||||
'dashboard' => false, // Being shown on the dashboard rather than settings page?
|
||||
'heading' => '', // Optional heading.
|
||||
'extra' => '', // Optional extra content to be shown in paragraph below message.
|
||||
'links' => array(), // Optional list of links to be shown at bottom of notice.
|
||||
'dismiss_url' => '', // Optional dismiss URL override.
|
||||
'created_at' => 0, // Optional timestamp, defaults to now.
|
||||
'short' => '', // Short version of the message.
|
||||
);
|
||||
|
||||
$notice = array_intersect_key( array_merge( $defaults, $args ), $defaults );
|
||||
$notice['message'] = $message;
|
||||
$notice['triggered_by'] = get_current_user_id();
|
||||
$notice['created_at'] = empty( $notice['created_at'] ) ? time() : $notice['created_at'];
|
||||
|
||||
if ( $notice['custom_id'] ) {
|
||||
$notice['id'] = $notice['custom_id'];
|
||||
} else {
|
||||
$notice['id'] = apply_filters( 'as3cf_notice_id_prefix', 'as3cf-notice-' ) . sha1( trim( $notice['message'] ) . trim( $notice['lock_key'] ) );
|
||||
}
|
||||
|
||||
return $notice;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a notice
|
||||
*
|
||||
* @param string $message
|
||||
* @param array $args
|
||||
*
|
||||
* @return string notice ID
|
||||
*/
|
||||
public function add_notice( $message, $args = array() ) {
|
||||
$notice = $this->build_notice( $message, $args );
|
||||
|
||||
$this->save_notice( $notice );
|
||||
|
||||
return $notice['id'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Save a notice
|
||||
*
|
||||
* @param array $notice
|
||||
*/
|
||||
protected function save_notice( $notice ) {
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
if ( $notice['only_show_to_user'] ) {
|
||||
$notices = get_user_meta( $user_id, 'as3cf_notices', true );
|
||||
} else {
|
||||
$notices = get_site_transient( 'as3cf_notices' );
|
||||
}
|
||||
|
||||
if ( ! is_array( $notices ) ) {
|
||||
$notices = array();
|
||||
}
|
||||
|
||||
// If the notice has not changed, don't update it.
|
||||
if (
|
||||
! empty( $notices ) &&
|
||||
array_key_exists( $notice['id'], $notices ) &&
|
||||
empty( AS3CF_Utils::array_diff_assoc_recursive( $notice, $notices[ $notice['id'] ] ) )
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notices[ $notice['id'] ] = $notice;
|
||||
|
||||
if ( $notice['only_show_to_user'] ) {
|
||||
update_user_meta( $user_id, 'as3cf_notices', $notices );
|
||||
} else {
|
||||
set_site_transient( 'as3cf_notices', $notices );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a notice
|
||||
*
|
||||
* @param array $notice
|
||||
*/
|
||||
public function remove_notice( $notice ) {
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
if ( $notice['only_show_to_user'] ) {
|
||||
$notices = get_user_meta( $user_id, 'as3cf_notices', true );
|
||||
} else {
|
||||
$notices = get_site_transient( 'as3cf_notices' );
|
||||
}
|
||||
|
||||
if ( ! is_array( $notices ) ) {
|
||||
$notices = array();
|
||||
}
|
||||
|
||||
if ( array_key_exists( $notice['id'], $notices ) ) {
|
||||
unset( $notices[ $notice['id'] ] );
|
||||
|
||||
if ( $notice['only_show_to_user'] ) {
|
||||
$this->update_user_meta( $user_id, 'as3cf_notices', $notices );
|
||||
} else {
|
||||
$this->set_site_transient( 'as3cf_notices', $notices );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a notice by it's ID
|
||||
*
|
||||
* @param string $notice_id
|
||||
*/
|
||||
public function remove_notice_by_id( $notice_id ) {
|
||||
$notice = $this->find_notice_by_id( $notice_id );
|
||||
if ( $notice ) {
|
||||
$this->remove_notice( $notice );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss a notice
|
||||
*
|
||||
* @param string $notice_id
|
||||
*/
|
||||
public function dismiss_notice( $notice_id ) {
|
||||
if ( empty( $notice_id ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
$notice = $this->find_notice_by_id( $notice_id );
|
||||
if ( $notice ) {
|
||||
if ( $notice['only_show_to_user'] ) {
|
||||
$notices = get_user_meta( $user_id, 'as3cf_notices', true );
|
||||
unset( $notices[ $notice['id'] ] );
|
||||
|
||||
$this->update_user_meta( $user_id, 'as3cf_notices', $notices );
|
||||
} else {
|
||||
$dismissed_notices = $this->get_dismissed_notices( $user_id );
|
||||
|
||||
if ( ! in_array( $notice['id'], $dismissed_notices ) ) {
|
||||
$dismissed_notices[] = $notice['id'];
|
||||
update_user_meta( $user_id, 'as3cf_dismissed_notices', $dismissed_notices );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a notice has been dismissed for the current user
|
||||
*
|
||||
* @param null|int $user_id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_dismissed_notices( $user_id = null ) {
|
||||
if ( is_null( $user_id ) ) {
|
||||
$user_id = get_current_user_id();
|
||||
}
|
||||
|
||||
$dismissed_notices = get_user_meta( $user_id, 'as3cf_dismissed_notices', true );
|
||||
if ( ! is_array( $dismissed_notices ) ) {
|
||||
$dismissed_notices = array();
|
||||
}
|
||||
|
||||
return $dismissed_notices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-dismiss a notice for a user
|
||||
*
|
||||
* @param string $notice_id
|
||||
* @param null|int $user_id
|
||||
* @param null|array $dismissed_notices
|
||||
*/
|
||||
public function undismiss_notice_for_user( $notice_id, $user_id = null, $dismissed_notices = null ) {
|
||||
if ( is_null( $user_id ) ) {
|
||||
$user_id = get_current_user_id();
|
||||
}
|
||||
|
||||
if ( is_null( $dismissed_notices ) ) {
|
||||
$dismissed_notices = $this->get_dismissed_notices( $user_id );
|
||||
}
|
||||
|
||||
$key = array_search( $notice_id, $dismissed_notices );
|
||||
unset( $dismissed_notices[ $key ] );
|
||||
|
||||
$this->update_user_meta( $user_id, 'as3cf_dismissed_notices', $dismissed_notices );
|
||||
}
|
||||
|
||||
/**
|
||||
* Un-dismiss a notice for all users that have dismissed it
|
||||
*
|
||||
* @param string $notice_id
|
||||
*/
|
||||
public function undismiss_notice_for_all( $notice_id ) {
|
||||
$args = array(
|
||||
'meta_key' => 'as3cf_dismissed_notices',
|
||||
'meta_value' => $notice_id,
|
||||
'meta_compare' => 'LIKE',
|
||||
);
|
||||
|
||||
$users = get_users( $args );
|
||||
|
||||
foreach ( $users as $user ) {
|
||||
$this->undismiss_notice_for_user( $notice_id, $user->ID );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a notice by it's ID
|
||||
*
|
||||
* @param string $notice_id
|
||||
*
|
||||
* @return array|null
|
||||
*/
|
||||
public function find_notice_by_id( $notice_id ) {
|
||||
if ( empty( $notice_id ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
|
||||
$user_notices = get_user_meta( $user_id, 'as3cf_notices', true );
|
||||
if ( ! is_array( $user_notices ) ) {
|
||||
$user_notices = array();
|
||||
}
|
||||
|
||||
$global_notices = get_site_transient( 'as3cf_notices' );
|
||||
if ( ! is_array( $global_notices ) ) {
|
||||
$global_notices = array();
|
||||
}
|
||||
$notices = array_merge( $user_notices, $global_notices );
|
||||
|
||||
if ( array_key_exists( $notice_id, $notices ) ) {
|
||||
return $notices[ $notice_id ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get notices that should be shown.
|
||||
*
|
||||
* @param string $tab Optionally restrict to notifications for a specific tab.
|
||||
* @param bool $all_tabs Optionally return all tab specific notices regardless of tab.
|
||||
* Only applies if $tab is not empty.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_notices( $tab = '', $all_tabs = false ) {
|
||||
$notices = array();
|
||||
|
||||
if ( empty( $tab ) ) {
|
||||
// Callbacks with no $tab property return empty string, so convert to bool.
|
||||
$tab = false;
|
||||
}
|
||||
|
||||
$user_id = get_current_user_id();
|
||||
$dismissed_notices = get_user_meta( $user_id, 'as3cf_dismissed_notices', true );
|
||||
if ( ! is_array( $dismissed_notices ) ) {
|
||||
$dismissed_notices = array();
|
||||
}
|
||||
|
||||
$user_notices = get_user_meta( $user_id, 'as3cf_notices', true );
|
||||
$user_notices = $this->cleanup_corrupt_user_notices( $user_id, $user_notices );
|
||||
if ( is_array( $user_notices ) && ! empty( $user_notices ) ) {
|
||||
foreach ( $user_notices as $notice ) {
|
||||
if ( $this->should_show_notice( $notice, $dismissed_notices, $tab, $all_tabs ) ) {
|
||||
$notices[] = $notice;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$global_notices = get_site_transient( 'as3cf_notices' );
|
||||
if ( is_array( $global_notices ) && ! empty( $global_notices ) ) {
|
||||
foreach ( $global_notices as $notice ) {
|
||||
if ( $this->should_show_notice( $notice, $dismissed_notices, $tab, $all_tabs ) ) {
|
||||
$notices[] = $notice;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter retrieved notices.
|
||||
*
|
||||
* @param array $notices
|
||||
* @param string $tab Notices have been optionally restricted to those for a specific tab.
|
||||
* @param bool $all_tabs All tab specific notices have optionally been retrieved regardless of tab.
|
||||
* Only applies if $tab is not empty.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
return apply_filters( 'as3cf_get_notices', $notices, $tab, $all_tabs );
|
||||
}
|
||||
|
||||
/**
|
||||
* Show notices via the standard WordPress mechanism.
|
||||
*
|
||||
* @param string $tab Optionally restrict to notifications for a specific tab.
|
||||
*/
|
||||
public function admin_notices( $tab = '' ) {
|
||||
$plugin_name = $this->as3cf->get_plugin_name();
|
||||
|
||||
foreach ( $this->get_notices( $tab ) as $notice ) {
|
||||
// Admin dashboard notices need to specify which plugin they relate to.
|
||||
$notice['message'] = '<strong>' . $plugin_name . '</strong> — ' . $notice['message'];
|
||||
|
||||
if ( 'info' === $notice['type'] ) {
|
||||
$notice['type'] = 'notice-info';
|
||||
}
|
||||
|
||||
// TODO: Maybe remove this unused functionality?
|
||||
if ( ! empty( $notice['pre_render_callback'] ) && is_callable( $notice['pre_render_callback'] ) ) {
|
||||
call_user_func( $notice['pre_render_callback'] );
|
||||
}
|
||||
|
||||
$this->as3cf->render_view( 'notice', $notice );
|
||||
|
||||
// Flash notices are only relevant in this context and can be removed once shown.
|
||||
if ( $notice['flash'] ) {
|
||||
$this->remove_notice( $notice );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If an API response is an array without any notifications, add in the current notifications.
|
||||
*
|
||||
* @param mixed $response
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function api_response( $response ) {
|
||||
if ( empty( $response ) || is_wp_error( $response ) || ! is_array( $response ) || isset( $response['notifications'] ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response['notifications'] = $this->get_notices( '', true );
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* If it exists, return the short version of the notice message.
|
||||
* If not, return the standard message.
|
||||
*
|
||||
* @param array $notice
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_short_message( array $notice ): string {
|
||||
if ( ! empty( $notice['short'] ) ) {
|
||||
return $notice['short'];
|
||||
}
|
||||
|
||||
return $notice['message'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Cleanup corrupt user notices. Corrupt notices start with a
|
||||
* numerically indexed array, opposed to string ID
|
||||
*
|
||||
* @param int $user_id
|
||||
* @param array $notices
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function cleanup_corrupt_user_notices( $user_id, $notices ) {
|
||||
if ( ! is_array( $notices ) || empty( $notices ) ) {
|
||||
return $notices;
|
||||
}
|
||||
|
||||
foreach ( $notices as $key => $notice ) {
|
||||
if ( is_int( $key ) ) {
|
||||
// Corrupt, remove
|
||||
unset( $notices[ $key ] );
|
||||
|
||||
$this->update_user_meta( $user_id, 'as3cf_notices', $notices );
|
||||
}
|
||||
}
|
||||
|
||||
return $notices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the given notice be shown?
|
||||
*
|
||||
* @param array $notice The notice.
|
||||
* @param array $dismissed_notices Notices already dismissed by user.
|
||||
* @param string|bool $tab Optionally restrict to notifications for a specific tab.
|
||||
* @param bool $all_tabs Optionally return all tab specific notices regardless of tab.
|
||||
* Only applies if $tab is not empty.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function should_show_notice( $notice, $dismissed_notices, $tab, $all_tabs ) {
|
||||
if ( $notice['only_show_in_settings'] && ! $this->as3cf->our_screen() && ! AS3CF_Utils::is_rest_api() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $notice['only_show_to_user'] && in_array( $notice['id'], $dismissed_notices ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! isset( $notice['only_show_on_tab'] ) && false !== $tab ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( isset( $notice['only_show_on_tab'] ) && $tab !== $notice['only_show_on_tab'] && true !== $all_tabs ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! $this->check_capability_for_notice( $notice ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! empty( $notice['lock_key'] ) && class_exists( 'AS3CF_Tool' ) && AS3CF_Tool::lock_key_exists( $notice['lock_key'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the user has the correct capabilities for the notice to be displayed.
|
||||
*
|
||||
* @param array $notice
|
||||
*
|
||||
* @return bool|mixed
|
||||
*/
|
||||
protected function check_capability_for_notice( $notice ) {
|
||||
if ( ! isset( $notice['user_capabilities'] ) || empty( $notice['user_capabilities'] ) ) {
|
||||
// No capability restrictions, show the notice
|
||||
return true;
|
||||
}
|
||||
|
||||
$caps = $notice['user_capabilities'];
|
||||
|
||||
if ( 2 === count( $caps ) && isset( $GLOBALS[ $caps[0] ] ) && is_callable( array( $GLOBALS[ $caps[0] ], $caps[1] ) ) ) {
|
||||
// Handle callback passed for capabilities
|
||||
return call_user_func( array( $GLOBALS[ $caps[0] ], $caps[1] ) );
|
||||
}
|
||||
|
||||
foreach ( $caps as $cap ) {
|
||||
if ( is_string( $cap ) && ! current_user_can( $cap ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue notice scripts in the admin
|
||||
*/
|
||||
public function enqueue_notice_scripts() {
|
||||
$this->as3cf->enqueue_style( 'as3cf-notice', 'assets/css/notice' );
|
||||
$this->as3cf->enqueue_script( 'as3cf-notice', 'assets/js/notice', array( 'jquery' ) );
|
||||
|
||||
wp_localize_script( 'as3cf-notice', 'as3cf_notice', array(
|
||||
'strings' => array(
|
||||
'dismiss_notice_error' => __( 'Error dismissing notice.', 'amazon-s3-and-cloudfront' ),
|
||||
),
|
||||
'nonces' => array(
|
||||
'dismiss_notice' => wp_create_nonce( 'as3cf-dismiss-notice' ),
|
||||
),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for AJAX request to dismiss a notice
|
||||
*/
|
||||
public function ajax_dismiss_notice() {
|
||||
$this->as3cf->verify_ajax_request();
|
||||
|
||||
if ( empty( $_POST['notice_id'] ) ) {
|
||||
$out = array( 'error' => __( 'Invalid notice ID.', 'amazon-s3-and-cloudfront' ) );
|
||||
$this->as3cf->end_ajax( $out );
|
||||
}
|
||||
|
||||
$notice_id = sanitize_text_field( $_POST['notice_id'] );
|
||||
|
||||
if ( empty( $notice_id ) ) {
|
||||
$out = array( 'error' => __( 'Invalid notice ID.', 'amazon-s3-and-cloudfront' ) );
|
||||
$this->as3cf->end_ajax( $out );
|
||||
}
|
||||
|
||||
$this->dismiss_notice( $notice_id );
|
||||
|
||||
$out = array(
|
||||
'success' => '1',
|
||||
);
|
||||
$this->as3cf->end_ajax( $out );
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to update/delete user meta
|
||||
*
|
||||
* @param int $user_id
|
||||
* @param string $key
|
||||
* @param array $value
|
||||
*/
|
||||
protected function update_user_meta( $user_id, $key, $value ) {
|
||||
if ( empty( $value ) ) {
|
||||
delete_user_meta( $user_id, $key );
|
||||
} else {
|
||||
update_user_meta( $user_id, $key, $value );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to update/delete site transient
|
||||
*
|
||||
* @param string $key
|
||||
* @param array $value
|
||||
*/
|
||||
protected function set_site_transient( $key, $value ) {
|
||||
if ( empty( $value ) ) {
|
||||
delete_site_transient( $key );
|
||||
} else {
|
||||
set_site_transient( $key, $value );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
558
classes/as3cf-plugin-base.php
Normal file
558
classes/as3cf-plugin-base.php
Normal file
@@ -0,0 +1,558 @@
|
||||
<?php
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Settings_Interface;
|
||||
use DeliciousBrains\WP_Offload_Media\Settings_Trait;
|
||||
|
||||
abstract class AS3CF_Plugin_Base implements Settings_Interface {
|
||||
use Settings_Trait;
|
||||
|
||||
const DBRAINS_URL = 'https://deliciousbrains.com';
|
||||
const WPE_URL = 'https://wpengine.com';
|
||||
|
||||
const SETTINGS_KEY = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $settings_constants = array();
|
||||
|
||||
protected static $plugin_page = 'amazon-s3-and-cloudfront';
|
||||
|
||||
protected $plugin_file_path;
|
||||
protected $plugin_dir_path;
|
||||
protected $plugin_slug;
|
||||
protected $plugin_basename;
|
||||
protected $plugin_version;
|
||||
protected $plugin_name;
|
||||
|
||||
/**
|
||||
* Initiate plugin
|
||||
*
|
||||
* @param string $plugin_file_path
|
||||
*/
|
||||
function __construct( $plugin_file_path ) {
|
||||
$this->plugin_file_path = $plugin_file_path;
|
||||
$this->plugin_dir_path = rtrim( plugin_dir_path( $plugin_file_path ), '/' );
|
||||
$this->plugin_basename = plugin_basename( $plugin_file_path );
|
||||
|
||||
if ( $this->plugin_slug && isset( $GLOBALS['aws_meta'][ $this->plugin_slug ]['version'] ) ) {
|
||||
$this->plugin_version = $GLOBALS['aws_meta'][ $this->plugin_slug ]['version'];
|
||||
}
|
||||
|
||||
$plugin_headers = array();
|
||||
|
||||
if ( is_admin() ) {
|
||||
if ( ! function_exists( 'get_plugin_data' ) ) {
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
}
|
||||
$plugin_headers = get_plugin_data( $plugin_file_path, false, false );
|
||||
}
|
||||
|
||||
// Fallback to generic plugin name if it can't be retrieved from the plugin headers.
|
||||
$this->plugin_name = empty( $plugin_headers['Name'] ) ? 'WP Offload Media' : $plugin_headers['Name'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for plugin name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_plugin_name() {
|
||||
return $this->plugin_name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for plugin version
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_plugin_version() {
|
||||
return $this->plugin_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for plugin slug
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_plugin_slug() {
|
||||
return $this->plugin_slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for plugin basename
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_plugin_basename() {
|
||||
return $this->plugin_basename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for plugin file path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_plugin_file_path() {
|
||||
return $this->plugin_file_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for plugin dir path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_plugin_dir_path() {
|
||||
return $this->plugin_dir_path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Accessor for plugin sdks dir path
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_plugin_sdks_dir_path() {
|
||||
return $this->get_plugin_dir_path() . '/vendor';
|
||||
}
|
||||
|
||||
/**
|
||||
* Which page name should we use?
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_plugin_pagenow() {
|
||||
return is_multisite() ? 'settings.php' : 'options-general.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific setting.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_setting( string $key, $default = '' ) {
|
||||
$this->get_settings();
|
||||
|
||||
if ( isset( $this->settings[ $key ] ) ) {
|
||||
$setting = $this->settings[ $key ];
|
||||
} else {
|
||||
$setting = $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the setting value retrieved for the given key.
|
||||
*
|
||||
* @param mixed $setting
|
||||
* @param string $key
|
||||
*/
|
||||
return apply_filters( 'as3cf_get_setting', $setting, $key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific setting from the core plugin.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_core_setting( string $key, $default = '' ) {
|
||||
return $this->get_setting( $key, $default );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add or overwrite new style defined values with legacy value.
|
||||
*
|
||||
* Should be overridden by concrete plugin classes.
|
||||
*
|
||||
* @param array $defines
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_legacy_defined_settings( array $defines ): array {
|
||||
return $defines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Which defined settings keys are from the standard settings constant?
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_non_legacy_defined_settings_keys(): array {
|
||||
static $keys = null;
|
||||
|
||||
if ( is_null( $keys ) ) {
|
||||
$keys = array_keys( array_diff_key( $this->get_defined_settings(), $this->get_legacy_defined_settings( array() ) ) );
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allowed settings keys for this plugin.
|
||||
* Meant to be overridden in child classes.
|
||||
*
|
||||
* @param bool $include_legacy Should legacy keys be included? Optional, default false.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_allowed_settings_keys( bool $include_legacy = false ): array {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the settings that should not be shown once saved.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_sensitive_settings(): array {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the blacklisted settings for monitoring changes in defines.
|
||||
* These settings will not be saved in the database.
|
||||
* Meant to be overridden in child classes.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_monitored_settings_blacklist(): array {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* List of settings that should skip full sanitize.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_skip_sanitize_settings(): array {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* List of settings that should be treated as paths.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_path_format_settings(): array {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* List of settings that should be treated as directory paths.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_prefix_format_settings(): array {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* List of settings that should be treated as booleans.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_boolean_format_settings(): array {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a view template file
|
||||
*
|
||||
* @param string $view View filename without the extension
|
||||
* @param array $args Arguments to pass to the view
|
||||
*/
|
||||
public function render_view( $view, $args = array() ) {
|
||||
extract( $args );
|
||||
include $this->plugin_dir_path . '/view/' . $view . '.php';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to return the settings page URL for the plugin
|
||||
*
|
||||
* @param array $args
|
||||
* @param string $url_method To prepend to admin_url()
|
||||
* @param bool $escape Should we escape the URL
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_plugin_page_url( $args = array(), $url_method = 'network', $escape = true ) {
|
||||
$default_args = array(
|
||||
'page' => static::$plugin_page,
|
||||
);
|
||||
|
||||
$args = array_merge( $default_args, $args );
|
||||
|
||||
switch ( $url_method ) {
|
||||
case 'self':
|
||||
$base_url = self_admin_url( static::get_plugin_pagenow() );
|
||||
break;
|
||||
default:
|
||||
$base_url = network_admin_url( static::get_plugin_pagenow() );
|
||||
}
|
||||
|
||||
// Add a hash to the URL
|
||||
$hash = false;
|
||||
if ( isset( $args['hash'] ) ) {
|
||||
$hash = $args['hash'];
|
||||
unset( $args['hash'] );
|
||||
}
|
||||
|
||||
$url = add_query_arg( $args, $base_url );
|
||||
|
||||
if ( $hash ) {
|
||||
$url .= '#' . $hash;
|
||||
}
|
||||
|
||||
if ( $escape ) {
|
||||
$url = esc_url_raw( $url );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* The text for the plugin action link for the plugin on the plugins page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_plugin_action_settings_text() {
|
||||
return __( 'Settings', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a link to the plugin row for the plugin on the plugins page.
|
||||
* Needs to be implemented for an extending class using -
|
||||
* add_filter( 'plugin_action_links', array( $this, 'plugin_actions_settings_link' ), 10, 2 );
|
||||
*
|
||||
* @param array $links
|
||||
* @param string $file
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
function plugin_actions_settings_link( $links, $file ) {
|
||||
$url = $this->get_plugin_page_url();
|
||||
$text = $this->get_plugin_action_settings_text();
|
||||
|
||||
$settings_link = '<a href="' . $url . '">' . esc_html( $text ) . '</a>';
|
||||
|
||||
if ( $file == $this->plugin_basename ) {
|
||||
array_unshift( $links, $settings_link );
|
||||
}
|
||||
|
||||
return $links;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue script.
|
||||
*
|
||||
* @param string $handle
|
||||
* @param string $path
|
||||
* @param array $deps
|
||||
* @param bool $footer
|
||||
*/
|
||||
public function enqueue_script( $handle, $path, $deps = array(), $footer = true ) {
|
||||
$version = $this->get_asset_version();
|
||||
$suffix = $this->get_asset_suffix();
|
||||
|
||||
$src = plugins_url( $path . $suffix . '.js', $this->plugin_file_path );
|
||||
wp_enqueue_script( $handle, $src, $deps, $version, $footer );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue style.
|
||||
*
|
||||
* @param string $handle
|
||||
* @param string $path
|
||||
* @param array $deps
|
||||
*/
|
||||
public function enqueue_style( $handle, $path, $deps = array() ) {
|
||||
$version = $this->get_asset_version();
|
||||
|
||||
$src = plugins_url( $path . '.css', $this->plugin_file_path );
|
||||
wp_enqueue_style( $handle, $src, $deps, $version );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the version used for script enqueuing
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_asset_version() {
|
||||
return defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? time() : $this->plugin_version;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the filename suffix used for script enqueuing
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_asset_suffix() {
|
||||
return defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate Delicious Brains site URL with correct UTM tags.
|
||||
*
|
||||
* @param string $path
|
||||
* @param array $args
|
||||
* @param string $hash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function dbrains_url( $path, $args = array(), $hash = '' ) {
|
||||
$args = wp_parse_args( $args, array(
|
||||
'utm_medium' => 'insideplugin',
|
||||
'utm_source' => static::get_utm_source(),
|
||||
) );
|
||||
$args = array_map( 'urlencode', $args );
|
||||
$url = trailingslashit( self::DBRAINS_URL ) . ltrim( $path, '/' );
|
||||
$url = add_query_arg( $args, $url );
|
||||
|
||||
if ( $hash ) {
|
||||
$url .= '#' . $hash;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate WP Engine site URL with correct UTM tags.
|
||||
*
|
||||
* @param string $path
|
||||
* @param array $args
|
||||
* @param string $hash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function wpe_url( $path = '', $args = array(), $hash = '' ) {
|
||||
$args = wp_parse_args( $args, array(
|
||||
'utm_medium' => 'referral',
|
||||
'utm_source' => 'ome_plugin',
|
||||
'utm_campaign' => 'bx_prod_referral',
|
||||
) );
|
||||
$args = array_map( 'urlencode', $args );
|
||||
$url = trailingslashit( self::WPE_URL ) . ltrim( $path, '/' );
|
||||
$url = add_query_arg( $args, $url );
|
||||
|
||||
if ( $hash ) {
|
||||
$url .= '#' . $hash;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get UTM source for plugin.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function get_utm_source() {
|
||||
return 'AWS';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get UTM content for WP Engine URL.
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected static function get_wpe_url_utm_content( $content = 'plugin_footer_text' ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the My Account URL
|
||||
*
|
||||
* @param array $args
|
||||
* @param string $hash
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_my_account_url( $args = array(), $hash = '' ) {
|
||||
return $this->dbrains_url( '/my-account/', $args, $hash );
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets up hooks to alter the footer of our admin pages.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function init_admin_footer() {
|
||||
add_filter( 'admin_footer_text', array( $this, 'filter_admin_footer_text' ) );
|
||||
add_filter( 'update_footer', array( $this, 'filter_update_footer' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the admin footer text to add our own links.
|
||||
*
|
||||
* @param string $text
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function filter_admin_footer_text( $text ) {
|
||||
$product_link = AS3CF_Utils::dbrains_link(
|
||||
static::dbrains_url(
|
||||
'/wp-offload-media/',
|
||||
array( 'utm_campaign' => 'plugin_footer', 'utm_content' => 'footer_colophon' )
|
||||
),
|
||||
$this->plugin_name
|
||||
);
|
||||
|
||||
$wpe_link = AS3CF_Utils::dbrains_link(
|
||||
static::wpe_url(
|
||||
'',
|
||||
array( 'utm_content' => static::get_wpe_url_utm_content() )
|
||||
),
|
||||
'WP Engine'
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
/* translators: %1$s is a link to WP Offload Media's website, and %2$s is a link to WP Engine's website. */
|
||||
__( '%1$s is developed and maintained by %2$s.', 'amazon-s3-and-cloudfront' ),
|
||||
$product_link,
|
||||
$wpe_link
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the admin footer's WordPress version text to add our own links.
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function filter_update_footer( $content ) {
|
||||
$links[] = AS3CF_Utils::dbrains_link(
|
||||
static::dbrains_url(
|
||||
'/wp-offload-media/docs/',
|
||||
array( 'utm_campaign' => 'plugin_footer', 'utm_content' => 'footer_navigation' )
|
||||
),
|
||||
__( 'Documentation', 'amazon-s3-and-cloudfront' )
|
||||
);
|
||||
|
||||
$links[] = '<a href="' . static::get_plugin_page_url( array( 'hash' => '/support' ) ) . '">' . __( 'Support', 'amazon-s3-and-cloudfront' ) . '</a>';
|
||||
|
||||
$links[] = AS3CF_Utils::dbrains_link(
|
||||
static::dbrains_url(
|
||||
'/wp-offload-media/feedback/',
|
||||
array( 'utm_campaign' => 'plugin_footer', 'utm_content' => 'footer_navigation' )
|
||||
),
|
||||
__( 'Feedback', 'amazon-s3-and-cloudfront' )
|
||||
);
|
||||
|
||||
$links[] = AS3CF_Utils::dbrains_link(
|
||||
static::dbrains_url(
|
||||
'/wp-offload-media/whats-new/',
|
||||
array( 'utm_campaign' => 'plugin_footer', 'utm_content' => 'footer_navigation' )
|
||||
),
|
||||
$this->plugin_name . ' ' . $this->plugin_version,
|
||||
'whats-new'
|
||||
);
|
||||
|
||||
return join( ' ∙ ', $links );
|
||||
}
|
||||
}
|
||||
1015
classes/as3cf-plugin-compatibility.php
Normal file
1015
classes/as3cf-plugin-compatibility.php
Normal file
File diff suppressed because it is too large
Load Diff
1064
classes/as3cf-utils.php
Normal file
1064
classes/as3cf-utils.php
Normal file
File diff suppressed because it is too large
Load Diff
501
classes/filters/as3cf-local-to-s3.php
Normal file
501
classes/filters/as3cf-local-to-s3.php
Normal file
@@ -0,0 +1,501 @@
|
||||
<?php
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
|
||||
class AS3CF_Local_To_S3 extends AS3CF_Filter {
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setup() {
|
||||
parent::setup();
|
||||
|
||||
// EDD
|
||||
add_filter( 'edd_download_files', array( $this, 'filter_edd_download_files' ) );
|
||||
// Customizer
|
||||
add_filter( 'theme_mod_background_image', array( $this, 'filter_customizer_image' ) );
|
||||
add_filter( 'theme_mod_header_image', array( $this, 'filter_customizer_image' ) );
|
||||
add_filter( 'customize_value_custom_css', array( $this, 'filter_customize_value_custom_css' ), 10, 2 );
|
||||
add_filter( 'wp_get_custom_css', array( $this, 'filter_wp_get_custom_css' ), 10, 2 );
|
||||
// Posts
|
||||
add_action( 'the_post', array( $this, 'filter_post_data' ) );
|
||||
add_filter( 'content_pagination', array( $this, 'filter_content_pagination' ) );
|
||||
add_filter( 'the_content', array( $this, 'filter_post' ), 100 );
|
||||
add_filter( 'the_excerpt', array( $this, 'filter_post' ), 100 );
|
||||
add_filter( 'rss_enclosure', array( $this, 'filter_post' ), 100 );
|
||||
add_filter( 'content_edit_pre', array( $this, 'filter_post' ) );
|
||||
add_filter( 'excerpt_edit_pre', array( $this, 'filter_post' ) );
|
||||
add_filter( 'as3cf_filter_post_local_to_s3', array( $this, 'filter_post' ) ); // Backwards compatibility
|
||||
add_filter( 'as3cf_filter_post_local_to_provider', array( $this, 'filter_post' ) );
|
||||
// Widgets
|
||||
add_filter( 'widget_form_callback', array( $this, 'filter_widget_display' ) );
|
||||
add_filter( 'widget_display_callback', array( $this, 'filter_widget_display' ) );
|
||||
if ( function_exists( 'is_wp_version_compatible' ) && is_wp_version_compatible( '5.8' ) ) {
|
||||
add_filter( 'customize_value_widget_block', array( $this, 'filter_customize_value_widget_block' ) );
|
||||
add_filter( 'widget_block_content', array( $this, 'filter_widget_block_content' ) );
|
||||
}
|
||||
// Edit Media page
|
||||
add_filter( 'set_url_scheme', array( $this, 'set_url_scheme' ), 10, 3 );
|
||||
// Blocks
|
||||
if ( function_exists( 'is_wp_version_compatible' ) && is_wp_version_compatible( '5.9' ) ) {
|
||||
add_filter( 'render_block', array( $this, 'filter_post' ), 100 );
|
||||
add_filter( 'get_block_templates', array( $this, 'filter_get_block_templates' ), 100, 3 );
|
||||
add_filter( 'get_block_template', array( $this, 'filter_get_block_template' ), 100, 3 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter customize value custom CSS.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param WP_Customize_Custom_CSS_Setting $setting
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function filter_customize_value_custom_css( $value, $setting ) {
|
||||
return $this->filter_custom_css( $value, $setting->stylesheet );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter `wp_get_custom_css`.
|
||||
*
|
||||
* @param string $css
|
||||
* @param string $stylesheet
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function filter_wp_get_custom_css( $css, $stylesheet ) {
|
||||
return $this->filter_custom_css( $css, $stylesheet );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter post data.
|
||||
*
|
||||
* @param WP_Post $post
|
||||
*/
|
||||
public function filter_post_data( $post ) {
|
||||
global $pages;
|
||||
|
||||
$cache = $this->get_post_cache( $post->ID );
|
||||
$to_cache = array();
|
||||
|
||||
if ( is_array( $pages ) && 1 === count( $pages ) && ! empty( $pages[0] ) ) {
|
||||
// Post already filtered and available on global $page array, continue
|
||||
$post->post_content = $pages[0];
|
||||
} else {
|
||||
$post->post_content = $this->process_content( $post->post_content, $cache, $to_cache );
|
||||
}
|
||||
|
||||
$post->post_excerpt = $this->process_content( $post->post_excerpt, $cache, $to_cache );
|
||||
|
||||
$this->maybe_update_post_cache( $to_cache );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter content pagination.
|
||||
*
|
||||
* @param array $pages
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_content_pagination( $pages ) {
|
||||
$cache = $this->get_post_cache();
|
||||
$to_cache = array();
|
||||
|
||||
foreach ( $pages as $key => $page ) {
|
||||
$pages[ $key ] = $this->process_content( $page, $cache, $to_cache );
|
||||
}
|
||||
|
||||
$this->maybe_update_post_cache( $to_cache );
|
||||
|
||||
return $pages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter widget display.
|
||||
*
|
||||
* @param array $instance
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_widget_display( $instance ) {
|
||||
return $this->handle_widget( $instance );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the content of the block widget during initial load of the customizer.
|
||||
*
|
||||
* @param array $value The widget block.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_customize_value_widget_block( $value ) {
|
||||
return $this->handle_widget( $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the content of the block widget before output.
|
||||
*
|
||||
* @param string $content The widget content.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function filter_widget_block_content( $content ) {
|
||||
if ( empty( $content ) ) {
|
||||
return $content;
|
||||
}
|
||||
|
||||
$cache = $this->get_option_cache();
|
||||
$to_cache = array();
|
||||
|
||||
$changed_content = $this->process_content( $content, $cache, $to_cache );
|
||||
|
||||
if ( ! empty( $changed_content ) && $changed_content !== $content ) {
|
||||
$content = $changed_content;
|
||||
}
|
||||
|
||||
$this->maybe_update_option_cache( $to_cache );
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does URL need replacing?
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function url_needs_replacing( $url ) {
|
||||
if ( str_replace( $this->get_bare_upload_base_urls(), '', $url ) === $url ) {
|
||||
// Remote URL, no replacement needed
|
||||
return false;
|
||||
}
|
||||
|
||||
// Local URL, perform replacement
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URL
|
||||
*
|
||||
* @param array $item_source
|
||||
* @param null|string $object_key
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function get_url( $item_source, $object_key = null ) {
|
||||
if ( Item::is_empty_item_source( $item_source ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the provider URL for an item
|
||||
*
|
||||
* @param string|false $url Url for the item, false if no URL can be determined
|
||||
* @param array $item_source Associative array describing the item, guaranteed keys:-
|
||||
* id: source item's unique integer id
|
||||
* source_type: source item's string type identifier
|
||||
* @param string|null $object_key Object key (size) describing what sub file of an item to return url for
|
||||
*/
|
||||
return apply_filters( 'as3cf_get_provider_url_for_item_source', false, $item_source, $object_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get base URL.
|
||||
*
|
||||
* @param array $item_source
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
protected function get_base_url( $item_source ) {
|
||||
if ( Item::is_empty_item_source( $item_source ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the local URL for an item
|
||||
*
|
||||
* @param string|false $url Url for the item, false if no URL can be determined
|
||||
* @param array $item_source Associative array describing the item, guaranteed keys:-
|
||||
* id: source item's unique integer id
|
||||
* source_type: source item's string type identifier
|
||||
* @param string|null $object_key Object key (size) describing what sub file of an item to return url for
|
||||
*/
|
||||
return apply_filters( 'as3cf_get_local_url_for_item_source', false, $item_source, null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item source descriptor from URL.
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return bool|array
|
||||
*/
|
||||
public function get_item_source_from_url( $url ) {
|
||||
$results = $this->get_item_sources_from_urls( array( $url ) );
|
||||
|
||||
if ( empty( $results ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ( $results as $result ) {
|
||||
if ( $result ) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item source descriptors from URLs.
|
||||
*
|
||||
* @param array $urls
|
||||
*
|
||||
* @return array url => item source descriptor array (or false)
|
||||
*/
|
||||
protected function get_item_sources_from_urls( $urls ) {
|
||||
$results = array();
|
||||
|
||||
if ( empty( $urls ) ) {
|
||||
return $results;
|
||||
}
|
||||
|
||||
if ( ! is_array( $urls ) ) {
|
||||
$urls = array( $urls );
|
||||
}
|
||||
|
||||
$query_set = array();
|
||||
$paths = array();
|
||||
$full_urls = array();
|
||||
|
||||
// Quickly parse given URLs to add versions without size as we should lookup with size info first as that could be the "full" size.
|
||||
foreach ( $urls as $url ) {
|
||||
$query_set[] = $url;
|
||||
$size_removed = AS3CF_Utils::remove_size_from_filename( $url );
|
||||
|
||||
if ( $url !== $size_removed ) {
|
||||
$query_set[] = $size_removed;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ( $query_set as $url ) {
|
||||
// Path to search for in query set should be based on bare URL.
|
||||
$bare_url = AS3CF_Utils::remove_scheme( $url );
|
||||
// There can be multiple URLs in the query set that belong to the same full URL for the Media Library item.
|
||||
$full_url = AS3CF_Utils::remove_size_from_filename( $bare_url );
|
||||
|
||||
if ( isset( $this->query_cache[ $full_url ] ) ) {
|
||||
// ID already cached, use it.
|
||||
$results[ $url ] = $this->query_cache[ $full_url ];
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$path = AS3CF_Utils::decode_filename_in_path( ltrim( str_replace( $this->get_bare_upload_base_urls(), '', $bare_url ), '/' ) );
|
||||
|
||||
$paths[ $path ] = $full_url;
|
||||
$full_urls[ $full_url ][] = $url;
|
||||
}
|
||||
|
||||
if ( ! empty( $paths ) ) {
|
||||
$as3cf_items = Item::get_by_source_path( array_keys( $paths ) );
|
||||
|
||||
if ( ! empty( $as3cf_items ) ) {
|
||||
/* @var Item $as3cf_item */
|
||||
foreach ( $as3cf_items as $as3cf_item ) {
|
||||
// Each returned item may have matched on either the source_path or original_source_path.
|
||||
// Because the base image file name of a thumbnail might match the primary rather scaled or rotated full image
|
||||
// it's possible that both source paths are used by separate URLs.
|
||||
foreach ( array( $as3cf_item->source_path(), $as3cf_item->original_source_path() ) as $source_path ) {
|
||||
if ( ! empty( $paths[ $source_path ] ) ) {
|
||||
$matched_full_url = $paths[ $source_path ];
|
||||
|
||||
if ( ! empty( $full_urls[ $matched_full_url ] ) ) {
|
||||
$item_source = array(
|
||||
'id' => $as3cf_item->source_id(),
|
||||
'source_type' => $as3cf_item->source_type(),
|
||||
);
|
||||
|
||||
$this->query_cache[ $matched_full_url ] = $item_source;
|
||||
|
||||
foreach ( $full_urls[ $matched_full_url ] as $url ) {
|
||||
$results[ $url ] = $item_source;
|
||||
}
|
||||
unset( $full_urls[ $matched_full_url ] );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// No more item IDs found, set remaining results as false.
|
||||
if ( count( $query_set ) !== count( $results ) ) {
|
||||
foreach ( $full_urls as $full_url => $schema_urls ) {
|
||||
foreach ( $schema_urls as $url ) {
|
||||
if ( ! array_key_exists( $url, $results ) ) {
|
||||
$this->query_cache[ $full_url ] = false;
|
||||
$results[ $url ] = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize find value.
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function normalize_find_value( $url ) {
|
||||
return AS3CF_Utils::decode_filename_in_path( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize replace value.
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function normalize_replace_value( $url ) {
|
||||
return AS3CF_Utils::encode_filename_in_path( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Post process content.
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function post_process_content( $content ) {
|
||||
$content = AS3CF_Utils::maybe_fix_serialized_string( $content );
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre replace content.
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function pre_replace_content( $content ) {
|
||||
$uploads = wp_upload_dir();
|
||||
$base_url = AS3CF_Utils::remove_scheme( $uploads['baseurl'] );
|
||||
|
||||
return $this->remove_aws_query_strings( $content, $base_url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Each time a URL is replaced this function is called to allow for logging or further updates etc.
|
||||
*
|
||||
* @param string $find URL with no scheme.
|
||||
* @param string $replace URL with no scheme.
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function url_replaced( $find, $replace, $content ) {
|
||||
if ( (bool) $this->as3cf->get_setting( 'force-https' ) ) {
|
||||
$content = str_replace( 'http:' . $replace, 'https:' . $replace, $content );
|
||||
}
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Might need to re-fix remote URL's schema if WordPress core has substituted in HTTP but HTTPS is required.
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $scheme
|
||||
* @param string $orig_scheme
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function set_url_scheme( $url, $scheme, $orig_scheme ) {
|
||||
if (
|
||||
'http' === $scheme && empty( $orig_scheme ) &&
|
||||
$this->as3cf->get_setting( 'force-https' ) &&
|
||||
$this->should_filter_content() &&
|
||||
! $this->url_needs_replacing( $url )
|
||||
) {
|
||||
// Check that it's one of ours and not external.
|
||||
$parts = AS3CF_Utils::parse_url( $url );
|
||||
|
||||
if ( empty( $parts['scheme'] ) || empty( $parts['host'] ) || 'http' !== $parts['scheme'] ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$ours = false;
|
||||
if ( $this->as3cf->get_setting( 'enable-delivery-domain' ) && $this->as3cf->get_setting( 'delivery-domain', '' ) === $parts['host'] ) {
|
||||
$ours = true;
|
||||
} elseif ( false !== strpos( $parts['host'], $this->as3cf->get_storage_provider()->get_domain() ) ) {
|
||||
$ours = true;
|
||||
}
|
||||
|
||||
if ( $ours ) {
|
||||
return substr_replace( $url, 'https', 0, 4 );
|
||||
}
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the array of queried block templates array after they've been fetched.
|
||||
*
|
||||
* @param WP_Block_Template[] $query_result Array of found block templates.
|
||||
* @param array $query Arguments to retrieve templates.
|
||||
* @param string $template_type wp_template or wp_template_part.
|
||||
*
|
||||
* @return WP_Block_Template[]
|
||||
*/
|
||||
public function filter_get_block_templates( $query_result, $query, $template_type ) {
|
||||
if ( empty( $query_result ) ) {
|
||||
return $query_result;
|
||||
}
|
||||
|
||||
foreach ( $query_result as $block_template ) {
|
||||
$block_template = $this->filter_get_block_template( $block_template, $block_template->id, $template_type );
|
||||
}
|
||||
|
||||
return $query_result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the queried block template object after it's been fetched.
|
||||
*
|
||||
* @param WP_Block_Template|null $block_template The found block template, or null if there isn't one.
|
||||
* @param string $id Template unique identifier (example: theme_slug//template_slug).
|
||||
* @param string $template_type Template type: `'wp_template'` or '`wp_template_part'`.
|
||||
*
|
||||
* @return WP_Block_Template|null
|
||||
*/
|
||||
public function filter_get_block_template( $block_template, $id, $template_type ) {
|
||||
if ( empty( $block_template ) ) {
|
||||
return $block_template;
|
||||
}
|
||||
|
||||
$content = $block_template->content;
|
||||
|
||||
if ( empty( $content ) ) {
|
||||
return $block_template;
|
||||
}
|
||||
|
||||
$content = $this->filter_post( $content );
|
||||
|
||||
if ( ! empty( $content ) && $content !== $block_template->content ) {
|
||||
$block_template->content = $content;
|
||||
}
|
||||
|
||||
return $block_template;
|
||||
}
|
||||
}
|
||||
266
classes/filters/as3cf-s3-to-local.php
Normal file
266
classes/filters/as3cf-s3-to-local.php
Normal file
@@ -0,0 +1,266 @@
|
||||
<?php
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
|
||||
class AS3CF_S3_To_Local extends AS3CF_Filter {
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setup() {
|
||||
parent::setup();
|
||||
|
||||
// EDD
|
||||
add_filter( 'edd_metabox_save_edd_download_files', array( $this, 'filter_edd_download_files' ) );
|
||||
// Customizer
|
||||
add_filter( 'pre_set_theme_mod_background_image', array( $this, 'filter_customizer_image' ), 10, 2 );
|
||||
add_filter( 'pre_set_theme_mod_header_image', array( $this, 'filter_customizer_image' ), 10, 2 );
|
||||
add_filter( 'pre_set_theme_mod_header_image_data', array( $this, 'filter_header_image_data' ), 10, 2 );
|
||||
add_filter( 'update_custom_css_data', array( $this, 'filter_update_custom_css_data' ), 10, 2 );
|
||||
// Posts
|
||||
add_filter( 'content_save_pre', array( $this, 'filter_post' ) );
|
||||
add_filter( 'excerpt_save_pre', array( $this, 'filter_post' ) );
|
||||
add_filter( 'as3cf_filter_post_s3_to_local', array( $this, 'filter_post' ) ); // Backwards compatibility
|
||||
add_filter( 'as3cf_filter_post_provider_to_local', array( $this, 'filter_post' ) );
|
||||
// Widgets
|
||||
add_filter( 'widget_update_callback', array( $this, 'filter_widget_save' ) );
|
||||
add_filter( 'pre_update_option_widget_block', array( $this, 'filter_widget_block_save' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter update custom CSS data.
|
||||
*
|
||||
* @param array $data
|
||||
* @param array $args
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_update_custom_css_data( $data, $args ) {
|
||||
$data['css'] = $this->filter_custom_css( $data['css'], $args['stylesheet'] );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter widget on save.
|
||||
*
|
||||
* @param array $instance
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_widget_save( $instance ) {
|
||||
return $this->handle_widget( $instance );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter widget block on save.
|
||||
*
|
||||
* @param array $value The new, unserialized option value.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_widget_block_save( $value ) {
|
||||
if ( empty( $value ) || ! is_array( $value ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
foreach ( $value as $idx => $section ) {
|
||||
$value[ $idx ] = $this->handle_widget( $section );
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should filter content.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function should_filter_content() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does URL need replacing?
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function url_needs_replacing( $url ) {
|
||||
if ( str_replace( $this->get_bare_upload_base_urls(), '', $url ) !== $url ) {
|
||||
// Local URL, no replacement needed.
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( str_replace( $this->get_remote_domains(), '', $url ) === $url ) {
|
||||
// Not a known remote URL, no replacement needed.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remote URL, perform replacement
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get URL
|
||||
*
|
||||
* @param array $item_source
|
||||
* @param null|string $object_key
|
||||
*
|
||||
* @return bool|string
|
||||
*/
|
||||
protected function get_url( $item_source, $object_key = null ) {
|
||||
if ( Item::is_empty_item_source( $item_source ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the local URL for an item
|
||||
*
|
||||
* @param string|false $url Url for the item, false if no URL can be determined
|
||||
* @param array $item_source Associative array describing the item, guaranteed keys:-
|
||||
* id: source item's unique integer id
|
||||
* source_type: source item's string type identifier
|
||||
* @param string|null $object_key Object key (size) describing what sub file of an item to return url for
|
||||
*/
|
||||
return apply_filters( 'as3cf_get_local_url_for_item_source', false, $item_source, $object_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get base URL.
|
||||
*
|
||||
* @param array $item_source
|
||||
*
|
||||
* @return string|false
|
||||
*/
|
||||
protected function get_base_url( $item_source ) {
|
||||
if ( Item::is_empty_item_source( $item_source ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the provider URL for an item
|
||||
*
|
||||
* @param string|false $url Url for the item, false if no URL can be determined
|
||||
* @param array $item_source Associative array describing the item, guaranteed keys:-
|
||||
* id: source item's unique integer id
|
||||
* source_type: source item's string type identifier
|
||||
* @param string|null $object_key Object key (size) describing what sub file of an item to return url for
|
||||
*/
|
||||
return apply_filters( 'as3cf_get_provider_url_for_item_source', false, $item_source, null );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item source descriptor from URL.
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return bool|array
|
||||
*/
|
||||
public function get_item_source_from_url( $url ) {
|
||||
// Result for sized URL already cached in request, return it.
|
||||
if ( isset( $this->query_cache[ $url ] ) ) {
|
||||
return $this->query_cache[ $url ];
|
||||
}
|
||||
|
||||
$item_source = Item::get_item_source_by_remote_url( $url );
|
||||
|
||||
if ( ! Item::is_empty_item_source( $item_source ) ) {
|
||||
$this->query_cache[ $url ] = $item_source;
|
||||
|
||||
return $item_source;
|
||||
}
|
||||
|
||||
$full_url = AS3CF_Utils::remove_size_from_filename( $url );
|
||||
|
||||
// If we've already tried to find this URL above because it didn't have a size suffix, cache and return.
|
||||
if ( $url === $full_url ) {
|
||||
$this->query_cache[ $url ] = $item_source;
|
||||
|
||||
return $item_source;
|
||||
}
|
||||
|
||||
// Result for URL already cached in request whether found or not, return it.
|
||||
if ( isset( $this->query_cache[ $full_url ] ) ) {
|
||||
return $this->query_cache[ $full_url ];
|
||||
}
|
||||
|
||||
$item_source = Item::get_item_source_by_remote_url( $full_url );
|
||||
|
||||
$this->query_cache[ $full_url ] = ! Item::is_empty_item_source( $item_source ) ? $item_source : false;
|
||||
|
||||
return $this->query_cache[ $full_url ];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item source descriptors from URLs.
|
||||
*
|
||||
* @param array $urls
|
||||
*
|
||||
* @return array url => item source descriptor (or false)
|
||||
*/
|
||||
protected function get_item_sources_from_urls( $urls ) {
|
||||
$results = array();
|
||||
|
||||
if ( empty( $urls ) ) {
|
||||
return $results;
|
||||
}
|
||||
|
||||
if ( ! is_array( $urls ) ) {
|
||||
$urls = array( $urls );
|
||||
}
|
||||
|
||||
foreach ( $urls as $url ) {
|
||||
$results[ $url ] = $this->get_item_source_from_url( $url );
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize find value.
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function normalize_find_value( $url ) {
|
||||
return AS3CF_Utils::encode_filename_in_path( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize replace value.
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function normalize_replace_value( $url ) {
|
||||
return AS3CF_Utils::decode_filename_in_path( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Post process content.
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function post_process_content( $content ) {
|
||||
$content = $this->remove_aws_query_strings( $content );
|
||||
$content = AS3CF_Utils::maybe_fix_serialized_string( $content );
|
||||
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre replace content.
|
||||
*
|
||||
* @param string $content
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function pre_replace_content( $content ) {
|
||||
return $content;
|
||||
}
|
||||
}
|
||||
280
classes/integrations/advanced-custom-fields.php
Normal file
280
classes/integrations/advanced-custom-fields.php
Normal file
@@ -0,0 +1,280 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Integrations;
|
||||
|
||||
use AS3CF_Error;
|
||||
use AS3CF_Utils;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Remove_Local_Handler;
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
use WP_Post;
|
||||
|
||||
class Advanced_Custom_Fields extends Integration {
|
||||
/**
|
||||
* Is installed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_installed(): bool {
|
||||
if ( class_exists( 'acf' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init integration.
|
||||
*/
|
||||
public function init() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setup() {
|
||||
/*
|
||||
* Content Filtering
|
||||
*/
|
||||
add_filter( 'acf/load_value/type=text', array( $this->as3cf->filter_local, 'filter_post' ) );
|
||||
add_filter( 'acf/load_value/type=textarea', array( $this->as3cf->filter_local, 'filter_post' ) );
|
||||
add_filter( 'acf/load_value/type=wysiwyg', array( $this->as3cf->filter_local, 'filter_post' ) );
|
||||
add_filter( 'acf/load_value/type=url', array( $this->as3cf->filter_local, 'filter_post' ) );
|
||||
add_filter( 'acf/load_value/type=link', array( $this, 'filter_link_local' ) );
|
||||
add_filter( 'acf/update_value/type=text', array( $this->as3cf->filter_provider, 'filter_post' ) );
|
||||
add_filter( 'acf/update_value/type=textarea', array( $this->as3cf->filter_provider, 'filter_post' ) );
|
||||
add_filter( 'acf/update_value/type=wysiwyg', array( $this->as3cf->filter_provider, 'filter_post' ) );
|
||||
add_filter( 'acf/update_value/type=url', array( $this->as3cf->filter_provider, 'filter_post' ) );
|
||||
add_filter( 'acf/update_value/type=link', array( $this, 'filter_link_provider' ) );
|
||||
|
||||
/*
|
||||
* Image Crop Add-on
|
||||
* https://en-gb.wordpress.org/plugins/acf-image-crop-add-on/
|
||||
*/
|
||||
if ( class_exists( 'acf_field_image_crop' ) ) {
|
||||
add_filter( 'wp_get_attachment_metadata', array( $this, 'download_image' ), 10, 2 );
|
||||
add_filter( 'sanitize_file_name', array( $this, 'remove_original_after_download' ) );
|
||||
}
|
||||
|
||||
/*
|
||||
* Rewrite URLs in field and field group config.
|
||||
*/
|
||||
add_filter( 'acf/load_fields', array( $this, 'acf_load_config' ) );
|
||||
add_filter( 'acf/load_field_group', array( $this, 'acf_load_config' ) );
|
||||
|
||||
/*
|
||||
* Supply missing data.
|
||||
*/
|
||||
add_filter( 'acf/filesize', array( $this, 'acf_filesize' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy back the S3 image for cropping
|
||||
*
|
||||
* @param array $data
|
||||
* @param int $post_id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function download_image( $data, $post_id ) {
|
||||
$this->maybe_download_image( $post_id );
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy back the S3 image
|
||||
*
|
||||
* @param int $post_id
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function maybe_download_image( $post_id ) {
|
||||
if ( false === $this->as3cf->plugin_compat->maybe_process_on_action( 'acf_image_crop_perform_crop', true ) ) {
|
||||
return $this->as3cf->_throw_error( 1, 'Not ACF crop process' );
|
||||
}
|
||||
|
||||
$file = get_attached_file( $post_id, true );
|
||||
|
||||
if ( file_exists( $file ) ) {
|
||||
return $this->as3cf->_throw_error( 2, 'File already exists' );
|
||||
}
|
||||
|
||||
$as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
|
||||
|
||||
if ( ! $as3cf_item ) {
|
||||
return $this->as3cf->_throw_error( 3, 'Attachment not offloaded' );
|
||||
}
|
||||
|
||||
$callers = debug_backtrace(); // phpcs:ignore
|
||||
foreach ( $callers as $caller ) {
|
||||
if ( isset( $caller['function'] ) && 'image_downsize' === $caller['function'] ) {
|
||||
// Don't copy when downsizing the image, which would result in bringing back
|
||||
// the newly cropped image from S3.
|
||||
return $this->as3cf->_throw_error( 4, 'Copying back cropped file' );
|
||||
}
|
||||
}
|
||||
|
||||
// Copy back the original file for cropping
|
||||
$result = $this->as3cf->plugin_compat->copy_provider_file_to_server( $as3cf_item, $file );
|
||||
|
||||
if ( false === $result ) {
|
||||
return $this->as3cf->_throw_error( 5, 'Copy back failed' );
|
||||
}
|
||||
|
||||
// Mark the attachment so we know to remove it later after the crop
|
||||
update_post_meta( $post_id, 'as3cf_acf_cropped_to_remove', true );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the original image downloaded for the cropping after it has been processed
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function remove_original_after_download( $filename ) {
|
||||
$this->maybe_remove_original_after_download();
|
||||
|
||||
return $filename;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the original image from the server
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function maybe_remove_original_after_download() {
|
||||
if ( false === $this->as3cf->plugin_compat->maybe_process_on_action( 'acf_image_crop_perform_crop', true ) ) {
|
||||
return $this->as3cf->_throw_error( 1, 'Not ACF crop process' );
|
||||
}
|
||||
|
||||
$original_attachment_id = $this->as3cf->filter_input( 'id', INPUT_POST, FILTER_VALIDATE_INT );
|
||||
|
||||
if ( ! isset( $original_attachment_id ) ) {
|
||||
// Can't find the original attachment id
|
||||
return $this->as3cf->_throw_error( 6, 'Attachment ID not available' );
|
||||
}
|
||||
|
||||
$as3cf_item = Media_Library_Item::get_by_source_id( $original_attachment_id );
|
||||
|
||||
if ( ! $as3cf_item ) {
|
||||
// Original attachment not on S3
|
||||
return $this->as3cf->_throw_error( 3, 'Attachment not offloaded' );
|
||||
}
|
||||
|
||||
if ( ! get_post_meta( $original_attachment_id, 'as3cf_acf_cropped_to_remove', true ) ) {
|
||||
// Original attachment should exist locally, no need to delete
|
||||
return $this->as3cf->_throw_error( 7, 'Attachment not to be removed from server' );
|
||||
}
|
||||
|
||||
// Remove the original file from the server
|
||||
$original_file = get_attached_file( $original_attachment_id, true );
|
||||
|
||||
$remove_local_handler = $this->as3cf->get_item_handler( Remove_Local_Handler::get_item_handler_key_name() );
|
||||
$remove_local_handler->handle( $as3cf_item, array( 'files_to_remove' => array( $original_file ) ) );
|
||||
|
||||
// Remove marker
|
||||
delete_post_meta( $original_attachment_id, 'as3cf_acf_cropped_to_remove' );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites URLs from local to remote inside ACF field and field group config. If the
|
||||
* rewriting process fails, it will return the original config.
|
||||
*
|
||||
* @handles acf/load_fields
|
||||
* @handles acf/load_field_group
|
||||
*
|
||||
* @param array $config
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function acf_load_config( array $config ): array {
|
||||
try {
|
||||
$filtered_config = AS3CF_Utils::maybe_unserialize( $this->as3cf->filter_local->filter_post( serialize( $config ) ) );
|
||||
} catch ( Exception $e ) {
|
||||
AS3CF_Error::log( __METHOD__ . ' ' . $e->getMessage() );
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
return is_array( $filtered_config ) ? $filtered_config : $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shortcut ACF's `filesize` call to prevent remote stream wrapper call
|
||||
* if attachment offloaded and removed from local and filesize metadata missing.
|
||||
*
|
||||
* ACF doesn't really use result, so if attachment offloaded and removed
|
||||
* but for some reason we too do not have the filesize, returning true
|
||||
* satisfies ACF's requirements.
|
||||
*
|
||||
* @param int|null $filesize The default filesize.
|
||||
* @param WP_Post $attachment The attachment post object we're looking for the filesize for.
|
||||
*
|
||||
* @return int|true
|
||||
*/
|
||||
public function acf_filesize( $filesize, $attachment ) {
|
||||
if ( ! empty( $filesize ) || ! is_a( $attachment, 'WP_Post' ) ) {
|
||||
return $filesize;
|
||||
}
|
||||
|
||||
$item = Media_Library_Item::get_by_source_id( $attachment->ID );
|
||||
|
||||
if ( empty( $item ) ) {
|
||||
return $filesize;
|
||||
}
|
||||
|
||||
$filesize = $item->get_filesize();
|
||||
|
||||
if ( empty( $filesize ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $filesize;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a link field's URL from local to provider.
|
||||
*
|
||||
* @param array $link
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_link_local( $link ) {
|
||||
if ( is_array( $link ) && ! empty( $link['url'] ) ) {
|
||||
$url = $this->as3cf->filter_local->filter_post( $link['url'] );
|
||||
|
||||
if ( ! empty( $url ) ) {
|
||||
$link['url'] = $url;
|
||||
}
|
||||
}
|
||||
|
||||
return $link;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter a link field's URL from provider to local.
|
||||
*
|
||||
* @param array $link
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_link_provider( $link ) {
|
||||
if ( is_array( $link ) && ! empty( $link['url'] ) ) {
|
||||
$url = $this->as3cf->filter_provider->filter_post( $link['url'] );
|
||||
|
||||
if ( ! empty( $url ) ) {
|
||||
$link['url'] = $url;
|
||||
}
|
||||
}
|
||||
|
||||
return $link;
|
||||
}
|
||||
}
|
||||
50
classes/integrations/core.php
Normal file
50
classes/integrations/core.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Integrations;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Remove_Local_Handler;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Upload_Handler;
|
||||
use WP_Error;
|
||||
|
||||
class Core extends Integration {
|
||||
/**
|
||||
* Is installed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_installed(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init integration.
|
||||
*/
|
||||
public function init() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setup() {
|
||||
add_action( 'as3cf_post_handle_item_' . Upload_Handler::get_item_handler_key_name(), array( $this, 'maybe_remove_local_files' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* After an upload completes, maybe remove local files.
|
||||
*
|
||||
* @handles as3cf_post_handle_item_upload
|
||||
*
|
||||
* @param bool|WP_Error $result Result for the action, either handled (true/false), or an error.
|
||||
* @param Item $as3cf_item The item that the action was being handled for.
|
||||
* @param array $options Handler dependent options that may have been set for the action.
|
||||
*/
|
||||
public function maybe_remove_local_files( $result, Item $as3cf_item, array $options ) {
|
||||
if ( ! is_wp_error( $result ) && $as3cf_item->id() && $this->as3cf->get_setting( 'remove-local-file', false ) && $as3cf_item->exists_locally() ) {
|
||||
$remove_local_handler = $this->as3cf->get_item_handler( Remove_Local_Handler::get_item_handler_key_name() );
|
||||
|
||||
$remove_local_handler->handle( $as3cf_item );
|
||||
}
|
||||
}
|
||||
}
|
||||
94
classes/integrations/integration-manager.php
Normal file
94
classes/integrations/integration-manager.php
Normal file
@@ -0,0 +1,94 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Integrations;
|
||||
|
||||
class Integration_Manager {
|
||||
|
||||
/**
|
||||
* @var Integration_Manager
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* @var Integration[]
|
||||
*/
|
||||
private $integrations;
|
||||
|
||||
/**
|
||||
* Protected constructor to prevent creating a new instance of the
|
||||
* class via the `new` operator from outside this class.
|
||||
*/
|
||||
protected function __construct() {
|
||||
$this->integrations = array();
|
||||
|
||||
add_action( 'as3cf_setup', array( $this, 'setup' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Make this class a singleton.
|
||||
*
|
||||
* Use this instead of __construct().
|
||||
*
|
||||
* @return Integration_Manager
|
||||
*/
|
||||
public static function get_instance() {
|
||||
if ( ! isset( static::$instance ) && ! ( self::$instance instanceof Integration_Manager ) ) {
|
||||
static::$instance = new Integration_Manager();
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for integration class instance
|
||||
*
|
||||
* @param string $integration_key
|
||||
*
|
||||
* @return Integration|null
|
||||
*/
|
||||
public function get_integration( $integration_key ) {
|
||||
if ( ! empty( $this->integrations[ $integration_key ] ) ) {
|
||||
return $this->integrations[ $integration_key ];
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register integration.
|
||||
*
|
||||
* @param string $integration_key
|
||||
* @param Integration $integration
|
||||
*/
|
||||
public function register_integration( $integration_key, Integration $integration ) {
|
||||
if ( $integration::is_installed() ) {
|
||||
$integration->init();
|
||||
|
||||
$this->integrations[ $integration_key ] = $integration;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set up the registered integrations.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function setup() {
|
||||
foreach ( $this->integrations as $integration ) {
|
||||
$integration->setup();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* As this class is a singleton it should not be clone-able.
|
||||
*/
|
||||
protected function __clone() {
|
||||
}
|
||||
|
||||
/**
|
||||
* As this class is a singleton it should not be able to be unserialized.
|
||||
*/
|
||||
public function __wakeup() {
|
||||
}
|
||||
|
||||
}
|
||||
53
classes/integrations/integration.php
Normal file
53
classes/integrations/integration.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Integrations;
|
||||
|
||||
use Amazon_S3_And_CloudFront;
|
||||
|
||||
abstract class Integration {
|
||||
|
||||
/**
|
||||
* @var Amazon_S3_And_CloudFront
|
||||
*/
|
||||
protected $as3cf;
|
||||
|
||||
/**
|
||||
* Integration constructor.
|
||||
*
|
||||
* @param Amazon_S3_And_CloudFront $as3cf
|
||||
*/
|
||||
public function __construct( Amazon_S3_And_CloudFront $as3cf ) {
|
||||
$this->as3cf = $as3cf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is installed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_installed(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this integration enabled?
|
||||
*
|
||||
* While the integration's dependencies may be installed,
|
||||
* it is possible that the integration is disabled for other reasons.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_enabled(): bool {
|
||||
return static::is_installed();
|
||||
}
|
||||
|
||||
/**
|
||||
* Init integration.
|
||||
*/
|
||||
abstract public function init();
|
||||
|
||||
/**
|
||||
* Set up the integration.
|
||||
*/
|
||||
abstract public function setup();
|
||||
}
|
||||
1715
classes/integrations/media-library.php
Normal file
1715
classes/integrations/media-library.php
Normal file
File diff suppressed because it is too large
Load Diff
174
classes/items/download-handler.php
Normal file
174
classes/items/download-handler.php
Normal file
@@ -0,0 +1,174 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Items;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Providers\Storage\Storage_Provider;
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
|
||||
class Download_Handler extends Item_Handler {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $item_handler_key = 'download';
|
||||
|
||||
/**
|
||||
* The default options that should be used if none supplied.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function default_options() {
|
||||
return array(
|
||||
'full_source_paths' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a manifest based on the item.
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param array $options
|
||||
*
|
||||
* @return Manifest
|
||||
*/
|
||||
protected function pre_handle( Item $as3cf_item, array $options ) {
|
||||
$manifest = new Manifest();
|
||||
$file_paths = array();
|
||||
|
||||
foreach ( $as3cf_item->objects() as $object_key => $object ) {
|
||||
$file = $as3cf_item->full_source_path( $object_key );
|
||||
|
||||
if ( 0 < count( $options['full_source_paths'] ) && ! in_array( $file, $options['full_source_paths'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$file_paths[ $object_key ] = $file;
|
||||
}
|
||||
|
||||
$file_paths = array_unique( $file_paths );
|
||||
|
||||
foreach ( $file_paths as $object_key => $file_path ) {
|
||||
if ( ! file_exists( $file_path ) ) {
|
||||
$manifest->objects[] = array(
|
||||
'args' => array(
|
||||
'Bucket' => $as3cf_item->bucket(),
|
||||
'Key' => $as3cf_item->provider_key( $object_key ),
|
||||
'SaveAs' => $file_path,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the downloads.
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param Manifest $manifest
|
||||
* @param array $options
|
||||
*
|
||||
* @return boolean|WP_Error
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function handle_item( Item $as3cf_item, Manifest $manifest, array $options ) {
|
||||
if ( ! empty( $manifest->objects ) ) {
|
||||
// This test is "late" so that we don't raise the error if the local files exist anyway.
|
||||
// If the provider of this item is different from what's currently configured,
|
||||
// we'll return an error.
|
||||
$current_provider = $this->as3cf->get_storage_provider();
|
||||
if ( ! is_null( $current_provider ) && $current_provider::get_provider_key_name() !== $as3cf_item->provider() ) {
|
||||
$error_msg = sprintf(
|
||||
__( '%1$s with ID %2$d is offloaded to a different provider than currently configured', 'amazon-s3-and-cloudfront' ),
|
||||
$this->as3cf->get_source_type_name( $as3cf_item->source_type() ),
|
||||
$as3cf_item->source_id()
|
||||
);
|
||||
|
||||
return $this->return_handler_error( $error_msg );
|
||||
} else {
|
||||
$provider_client = $this->as3cf->get_provider_client( $as3cf_item->region() );
|
||||
|
||||
foreach ( $manifest->objects as &$manifest_object ) {
|
||||
// Save object to a file.
|
||||
$result = $this->download_object( $provider_client, $manifest_object['args'] );
|
||||
|
||||
$manifest_object['download_result']['status'] = self::STATUS_OK;
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$manifest_object['download_result']['status'] = self::STATUS_FAILED;
|
||||
$manifest_object['download_result']['message'] = $result->get_error_message();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform post handle tasks. Log errors, update filesize totals etc.
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param Manifest $manifest
|
||||
* @param array $options
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
protected function post_handle( Item $as3cf_item, Manifest $manifest, array $options ) {
|
||||
$error_count = 0;
|
||||
|
||||
foreach ( $manifest->objects as $manifest_object ) {
|
||||
if ( self::STATUS_OK !== $manifest_object['download_result']['status'] ) {
|
||||
$error_count++;
|
||||
}
|
||||
}
|
||||
|
||||
if ( $error_count > 0 ) {
|
||||
$error_message = sprintf(
|
||||
__( 'There were %1$d errors downloading files for %2$s ID %3$d from bucket', 'amazon-s3-and-cloudfront' ),
|
||||
$error_count,
|
||||
$this->as3cf->get_source_type_name( $as3cf_item->source_type() ),
|
||||
$as3cf_item->source_id()
|
||||
);
|
||||
|
||||
return new WP_Error( 'download-error', $error_message );
|
||||
}
|
||||
|
||||
$as3cf_item->update_filesize_after_download_local();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Download an object from provider.
|
||||
*
|
||||
* @param Storage_Provider $provider_client
|
||||
* @param array $object
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
private function download_object( $provider_client, $object ) {
|
||||
// Make sure the local directory exists.
|
||||
$dir = dirname( $object['SaveAs'] );
|
||||
if ( ! is_dir( $dir ) && ! wp_mkdir_p( $dir ) ) {
|
||||
$error_msg = sprintf( __( 'The local directory %s does not exist and could not be created.', 'amazon-s3-and-cloudfront' ), $dir );
|
||||
$error_msg = sprintf( __( 'There was an error attempting to download the file %1$s from the bucket: %2$s', 'amazon-s3-and-cloudfront' ), $object['Key'], $error_msg );
|
||||
|
||||
return $this->return_handler_error( $error_msg );
|
||||
}
|
||||
|
||||
try {
|
||||
$provider_client->get_object( $object );
|
||||
} catch ( Exception $e ) {
|
||||
// If storage provider file doesn't exist, an empty local file will be created, clean it up.
|
||||
@unlink( $object['SaveAs'] ); //phpcs:ignore
|
||||
|
||||
$error_msg = sprintf( __( 'Error downloading %1$s from bucket: %2$s', 'amazon-s3-and-cloudfront' ), $object['Key'], $e->getMessage() );
|
||||
|
||||
return $this->return_handler_error( $error_msg );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
258
classes/items/item-handler.php
Normal file
258
classes/items/item-handler.php
Normal file
@@ -0,0 +1,258 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Items;
|
||||
|
||||
use Amazon_S3_And_CloudFront;
|
||||
use AS3CF_Error;
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
|
||||
/**
|
||||
* Class Item_Handler
|
||||
*
|
||||
* Base class for item handler classes.
|
||||
*
|
||||
* @package DeliciousBrains\WP_Offload_Media\Items
|
||||
*/
|
||||
abstract class Item_Handler {
|
||||
/**
|
||||
* Status codes
|
||||
*/
|
||||
const STATUS_OK = 'ok';
|
||||
const STATUS_FAILED = 'failed';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $item_handler_key;
|
||||
|
||||
/**
|
||||
* @var Amazon_S3_And_CloudFront
|
||||
*/
|
||||
protected $as3cf;
|
||||
|
||||
/**
|
||||
* AS3CF_Item_Handler constructor.
|
||||
*
|
||||
* @param Amazon_S3_And_CloudFront $as3cf
|
||||
*/
|
||||
public function __construct( Amazon_S3_And_CloudFront $as3cf ) {
|
||||
$this->as3cf = $as3cf;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the item handler key name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_item_handler_key_name() {
|
||||
return static::$item_handler_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* The default options that should be used if none supplied.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function default_options() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Main entrypoint for handling an item.
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param array $options
|
||||
*
|
||||
* @return boolean|WP_Error
|
||||
*/
|
||||
public function handle( Item $as3cf_item, array $options = array() ) {
|
||||
// Merge supplied option values into the defaults as long as supplied options are recognised.
|
||||
if ( empty( $options ) || ! is_array( $options ) ) {
|
||||
$options = array();
|
||||
}
|
||||
$options = array_merge( $this->default_options(), array_intersect_key( $options, $this->default_options() ) );
|
||||
|
||||
try {
|
||||
/**
|
||||
* Filter fires before handling an action on an item, allows action to be cancelled.
|
||||
*
|
||||
* This is a generic handler filter that includes the handler's key name as the last param.
|
||||
*
|
||||
* @param bool $cancel Should the action on the item be cancelled?
|
||||
* @param Item $as3cf_item The item that the action is being handled for.
|
||||
* @param array $options Handler dependent options that may have been set for the action.
|
||||
* @param array $handler_key_name The handler's key name as per `Item_Handler::get_item_handler_key_name()`.
|
||||
*
|
||||
* @see Item_Handler::get_item_handler_key_name()
|
||||
*/
|
||||
$cancel = apply_filters(
|
||||
'as3cf_pre_handle_item',
|
||||
/**
|
||||
* Filter fires before handling an action on an item, allows action to be cancelled.
|
||||
*
|
||||
* This is a handler specific filter whose name ends with the handler's key name.
|
||||
* Format is `as3cf_pre_handle_item_{item-handler-key-name}`.
|
||||
*
|
||||
* Example filter names:
|
||||
*
|
||||
* as3cf_pre_handle_item_upload
|
||||
* as3cf_pre_handle_item_download
|
||||
* as3cf_pre_handle_item_remove-local
|
||||
* as3cf_pre_handle_item_remove-provider
|
||||
* as3cf_pre_handle_item_update-acl
|
||||
*
|
||||
* For a more generic filter, use `as3cf_pre_handle_item`.
|
||||
*
|
||||
* @param bool $cancel Should the action on the item be cancelled?
|
||||
* @param Item $as3cf_item The item that the action is being handled for.
|
||||
* @param array $options Handler dependent options that may have been set for the action.
|
||||
*
|
||||
* @see Item_Handler::get_item_handler_key_name()
|
||||
*/
|
||||
apply_filters( 'as3cf_pre_handle_item_' . static::get_item_handler_key_name(), false, $as3cf_item, $options ),
|
||||
$as3cf_item,
|
||||
$options,
|
||||
static::get_item_handler_key_name()
|
||||
);
|
||||
} catch ( Exception $e ) {
|
||||
return $this->return_result( new WP_Error( $e->getMessage() ), $as3cf_item, $options );
|
||||
}
|
||||
|
||||
// Cancelled, let caller know that request was not handled.
|
||||
if ( false !== $cancel ) {
|
||||
// If something unexpected happened, let the caller know.
|
||||
if ( is_wp_error( $cancel ) ) {
|
||||
return $this->return_result( $cancel, $as3cf_item, $options );
|
||||
}
|
||||
|
||||
return $this->return_result( false, $as3cf_item, $options );
|
||||
}
|
||||
|
||||
$manifest = $this->pre_handle( $as3cf_item, $options );
|
||||
if ( is_wp_error( $manifest ) ) {
|
||||
return $this->return_result( $manifest, $as3cf_item, $options );
|
||||
}
|
||||
|
||||
// Nothing to do, let caller know that request was not handled.
|
||||
if ( empty( $manifest ) || empty( $manifest->objects ) ) {
|
||||
return $this->return_result( false, $as3cf_item, $options );
|
||||
}
|
||||
|
||||
$result = $this->handle_item( $as3cf_item, $manifest, $options );
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $this->return_result( $result, $as3cf_item, $options );
|
||||
}
|
||||
|
||||
$result = $this->post_handle( $as3cf_item, $manifest, $options );
|
||||
|
||||
return $this->return_result( $result, $as3cf_item, $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an Item and options to generate a Manifest for `handle_item`.
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param array $options
|
||||
*
|
||||
* @return Manifest|WP_Error
|
||||
*/
|
||||
abstract protected function pre_handle( Item $as3cf_item, array $options );
|
||||
|
||||
/**
|
||||
* Perform action for Item using given Manifest.
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param Manifest $manifest
|
||||
* @param array $options
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
abstract protected function handle_item( Item $as3cf_item, Manifest $manifest, array $options );
|
||||
|
||||
/**
|
||||
* Process results of `handle_item` as appropriate.
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param Manifest $manifest
|
||||
* @param array $options
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
abstract protected function post_handle( Item $as3cf_item, Manifest $manifest, array $options );
|
||||
|
||||
/**
|
||||
* Helper to record errors and return them or optional supplied value.
|
||||
*
|
||||
* @param string|WP_Error $error_msg An error message or already constructed WP_Error.
|
||||
* @param mixed|null $return Optional return value instead of WP_Error.
|
||||
*
|
||||
* @return mixed|WP_Error
|
||||
*/
|
||||
protected function return_handler_error( $error_msg, $return = null ) {
|
||||
if ( is_wp_error( $error_msg ) ) {
|
||||
foreach ( $error_msg->get_error_messages() as $msg ) {
|
||||
AS3CF_Error::Log( $msg );
|
||||
}
|
||||
} else {
|
||||
AS3CF_Error::log( $error_msg );
|
||||
}
|
||||
|
||||
if ( is_null( $return ) ) {
|
||||
return is_wp_error( $error_msg ) ? $error_msg : new WP_Error( 'exception', $error_msg );
|
||||
}
|
||||
|
||||
return $return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires a couple of actions to let interested parties know that a handler has returned a result.
|
||||
*
|
||||
* @param bool|WP_Error $result Result for the action, either handled (true/false), or an error.
|
||||
* @param Item $as3cf_item The item that the action was being handled for.
|
||||
* @param array $options Handler dependent options that may have been set for the action.
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
private function return_result( $result, Item $as3cf_item, array $options ) {
|
||||
/**
|
||||
* Action fires after attempting to handle an action on an item.
|
||||
*
|
||||
* This is a handler specific action whose name ends with the handler's key name.
|
||||
* Format is `as3cf_post_handle_item_{item-handler-key-name}`.
|
||||
*
|
||||
* Example filter names:
|
||||
*
|
||||
* as3cf_post_handle_item_upload
|
||||
* as3cf_post_handle_item_download
|
||||
* as3cf_post_handle_item_remove-local
|
||||
* as3cf_post_handle_item_remove-provider
|
||||
* as3cf_post_handle_item_update-acl
|
||||
*
|
||||
* For a more generic filter, use `as3cf_post_handle_item`.
|
||||
*
|
||||
* @param bool|WP_Error $result Result for the action, either handled (true/false), or an error.
|
||||
* @param Item $as3cf_item The item that the action was being handled for.
|
||||
* @param array $options Handler dependent options that may have been set for the action.
|
||||
*
|
||||
* @see Item_Handler::get_item_handler_key_name()
|
||||
*/
|
||||
do_action( 'as3cf_post_handle_item_' . static::get_item_handler_key_name(), $result, $as3cf_item, $options );
|
||||
|
||||
/**
|
||||
* Action fires after attempting to handle an action on an item.
|
||||
*
|
||||
* This is a generic handler action that includes the handler's key name as the last param.
|
||||
*
|
||||
* @param bool|WP_Error $result Result for the action, either handled (true/false), or an error.
|
||||
* @param Item $as3cf_item The item that the action was being handled for.
|
||||
* @param array $options Handler dependent options that may have been set for the action.
|
||||
* @param array $handler_key_name The handler's key name as per `Item_Handler::get_item_handler_key_name()`.
|
||||
*
|
||||
* @see Item_Handler::get_item_handler_key_name()
|
||||
*/
|
||||
do_action( 'as3cf_post_handle_item', $result, $as3cf_item, $options, static::get_item_handler_key_name() );
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
2197
classes/items/item.php
Normal file
2197
classes/items/item.php
Normal file
File diff suppressed because it is too large
Load Diff
10
classes/items/manifest.php
Normal file
10
classes/items/manifest.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Items;
|
||||
|
||||
class Manifest {
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $objects = array();
|
||||
}
|
||||
837
classes/items/media-library-item.php
Normal file
837
classes/items/media-library-item.php
Normal file
@@ -0,0 +1,837 @@
|
||||
<?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 <<<
|
||||
*/
|
||||
}
|
||||
44
classes/items/provider-test-item.php
Normal file
44
classes/items/provider-test-item.php
Normal file
@@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Items;
|
||||
|
||||
use WP_Error;
|
||||
|
||||
class Provider_Test_Item extends Media_Library_Item {
|
||||
/**
|
||||
* Source type name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_type_name = 'Provider Test Item';
|
||||
|
||||
/**
|
||||
* Internal source type identifier.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_type = 'provider-test';
|
||||
|
||||
/**
|
||||
* Overrides the parent implementation to avoid storing anything in the database.
|
||||
*
|
||||
* @param bool $update_duplicates
|
||||
*
|
||||
* @return int|WP_Error
|
||||
*/
|
||||
public function save( $update_duplicates = true ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the parent implementation. Return all paths unchanged.
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param array $paths
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function remove_duplicate_paths( Item $as3cf_item, $paths ): array {
|
||||
return $paths;
|
||||
}
|
||||
}
|
||||
209
classes/items/remove-local-handler.php
Normal file
209
classes/items/remove-local-handler.php
Normal file
@@ -0,0 +1,209 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Items;
|
||||
|
||||
use AS3CF_Error;
|
||||
|
||||
class Remove_Local_Handler extends Item_Handler {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $item_handler_key = 'remove-local';
|
||||
|
||||
/**
|
||||
* Keep track of individual files we've already attempted to remove.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $remove_blocked = array();
|
||||
|
||||
/**
|
||||
* Keep track of size of individual files we've already attempted to remove.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $removed_size = array();
|
||||
|
||||
/**
|
||||
* If remove the primary file, we want to update the 'filesize'.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $removed_primary_size = array();
|
||||
|
||||
/**
|
||||
* The default options that should be used if none supplied.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function default_options() {
|
||||
return array(
|
||||
'verify_exists_on_provider' => false,
|
||||
'provider_keys' => array(),
|
||||
'files_to_remove' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create manifest for local removal.
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param array $options
|
||||
*
|
||||
* @return Manifest
|
||||
*/
|
||||
protected function pre_handle( Item $as3cf_item, array $options ) {
|
||||
$manifest = new Manifest();
|
||||
$source_id = $as3cf_item->source_id();
|
||||
$primary_file = '';
|
||||
$files_to_remove = array();
|
||||
|
||||
// Note: Unable to use Item::full_size_paths() here
|
||||
// as source item's metadata may not be up-to-date yet.
|
||||
foreach ( $as3cf_item->objects() as $object_key => $object ) {
|
||||
$file = $as3cf_item->full_source_path( $object_key );
|
||||
|
||||
if ( in_array( $file, $this->remove_blocked ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( 0 < count( $options['files_to_remove'] ) && ! in_array( $file, $options['files_to_remove'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If needed, make sure this item exists among the provider keys.
|
||||
if ( true === $options['verify_exists_on_provider'] ) {
|
||||
if ( empty( $options['provider_keys'][ $source_id ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! in_array( $as3cf_item->provider_key( $object_key ), $options['provider_keys'][ $source_id ] ) ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ( file_exists( $file ) ) {
|
||||
$files_to_remove[] = $file;
|
||||
|
||||
if ( Item::primary_object_key() === $object_key ) {
|
||||
$primary_file = $file;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters array of local files before being removed from server.
|
||||
*
|
||||
* @param array $files_to_remove Array of paths to be removed
|
||||
* @param Item $as3cf_item The Item object
|
||||
* @param array $item_source Item source descriptor array
|
||||
*/
|
||||
$filtered_files_to_remove = apply_filters( 'as3cf_remove_local_files', $files_to_remove, $as3cf_item, $as3cf_item->get_item_source_array() );
|
||||
|
||||
// Ensure fileset is unique and does not contain files already blocked.
|
||||
$filtered_files_to_remove = array_unique( array_diff( $filtered_files_to_remove, $this->remove_blocked ) );
|
||||
|
||||
// If filter removes files from list, block attempts to remove them in later calls.
|
||||
$this->remove_blocked = array_merge( $this->remove_blocked, array_diff( $files_to_remove, $filtered_files_to_remove ) );
|
||||
|
||||
foreach ( $filtered_files_to_remove as $file ) {
|
||||
// Filter may have added some files to check for existence.
|
||||
if ( ! in_array( $file, $files_to_remove ) ) {
|
||||
if ( ! file_exists( $file ) ) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter individual files that might still be kept local.
|
||||
*
|
||||
* @param bool $preserve Should the file be kept on the server?
|
||||
* @param string $file Full path to the local file
|
||||
*/
|
||||
if ( false !== apply_filters( 'as3cf_preserve_file_from_local_removal', false, $file ) ) {
|
||||
$this->remove_blocked[] = $file;
|
||||
continue;
|
||||
}
|
||||
|
||||
$manifest->objects[] = array(
|
||||
'file' => $file,
|
||||
'size' => filesize( $file ),
|
||||
'is_primary' => $file === $primary_file,
|
||||
);
|
||||
}
|
||||
|
||||
return $manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete local files described in the manifest object array.
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param Manifest $manifest
|
||||
* @param array $options
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function handle_item( Item $as3cf_item, Manifest $manifest, array $options ) {
|
||||
foreach ( $manifest->objects as &$file_to_remove ) {
|
||||
$file = $file_to_remove['file'];
|
||||
|
||||
$file_to_remove['remove_result'] = array( 'status' => self::STATUS_OK );
|
||||
|
||||
//phpcs:ignore
|
||||
if ( ! @unlink( $file ) ) {
|
||||
$this->remove_blocked[] = $file;
|
||||
|
||||
$file_to_remove['remove_result']['status'] = self::STATUS_FAILED;
|
||||
$file_to_remove['remove_result']['message'] = "Error removing local file at $file";
|
||||
|
||||
if ( ! file_exists( $file ) ) {
|
||||
$file_to_remove['remove_result']['message'] = "Error removing local file. Couldn't find the file at $file";
|
||||
} elseif ( ! is_writable( $file ) ) {
|
||||
$file_to_remove['remove_result']['message'] = "Error removing local file. Ownership or permissions are mis-configured for $file";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform post handle tasks.
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param Manifest $manifest
|
||||
* @param array $options
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function post_handle( Item $as3cf_item, Manifest $manifest, array $options ) {
|
||||
if ( empty( $manifest->objects ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Assume we didn't touch the primary file.
|
||||
$this->removed_primary_size[ $as3cf_item->source_id() ] = 0;
|
||||
|
||||
foreach ( $manifest->objects as $file_to_remove ) {
|
||||
if ( $file_to_remove['remove_result']['status'] !== self::STATUS_OK ) {
|
||||
AS3CF_Error::log( $file_to_remove['remove_result']['message'] );
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( empty( $this->removed_size[ $as3cf_item->source_id() ] ) ) {
|
||||
$this->removed_size[ $as3cf_item->source_id() ] = $file_to_remove['size'];
|
||||
} else {
|
||||
$this->removed_size[ $as3cf_item->source_id() ] += $file_to_remove['size'];
|
||||
}
|
||||
|
||||
if ( $file_to_remove['is_primary'] ) {
|
||||
$this->removed_primary_size[ $as3cf_item->source_id() ] = $file_to_remove['size'];
|
||||
}
|
||||
}
|
||||
|
||||
$as3cf_item->update_filesize_after_remove_local( $this->removed_primary_size[ $as3cf_item->source_id() ], $this->removed_size[ $as3cf_item->source_id() ] );
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
154
classes/items/remove-provider-handler.php
Normal file
154
classes/items/remove-provider-handler.php
Normal file
@@ -0,0 +1,154 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Items;
|
||||
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
|
||||
class Remove_Provider_Handler extends Item_Handler {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $item_handler_key = 'remove-provider';
|
||||
|
||||
/**
|
||||
* The default options that should be used if none supplied.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function default_options() {
|
||||
return array(
|
||||
'object_keys' => array(),
|
||||
'offloaded_files' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create manifest for removal from provider.
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param array $options
|
||||
*
|
||||
* @return Manifest|WP_Error
|
||||
*/
|
||||
protected function pre_handle( Item $as3cf_item, array $options ) {
|
||||
$manifest = new Manifest();
|
||||
$paths = array();
|
||||
|
||||
if ( ! empty( $options['object_keys'] ) && ! is_array( $options['object_keys'] ) ) {
|
||||
return $this->return_handler_error( __( 'Invalid object_keys option provided.', 'amazon-s3-and-cloudfront' ) );
|
||||
}
|
||||
|
||||
if ( ! empty( $options['offloaded_files'] ) && ! is_array( $options['offloaded_files'] ) ) {
|
||||
return $this->return_handler_error( __( 'Invalid offloaded_files option provided.', 'amazon-s3-and-cloudfront' ) );
|
||||
}
|
||||
|
||||
if ( ! empty( $options['object_keys'] ) && ! empty( $options['offloaded_files'] ) ) {
|
||||
return $this->return_handler_error( __( 'Providing both object_keys and offloaded_files options is not supported.', 'amazon-s3-and-cloudfront' ) );
|
||||
}
|
||||
|
||||
if ( empty( $options['offloaded_files'] ) ) {
|
||||
foreach ( $as3cf_item->objects() as $object_key => $object ) {
|
||||
if ( 0 < count( $options['object_keys'] ) && ! in_array( $object_key, $options['object_keys'] ) ) {
|
||||
continue;
|
||||
}
|
||||
$paths[ $object_key ] = $as3cf_item->full_source_path( $object_key );
|
||||
}
|
||||
} else {
|
||||
foreach ( $options['offloaded_files'] as $filename => $object ) {
|
||||
$paths[ $filename ] = $as3cf_item->full_source_path_for_filename( $filename );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters array of source files before being removed from provider.
|
||||
*
|
||||
* @param array $paths Array of local paths to be removed from provider
|
||||
* @param Item $as3cf_item The Item object
|
||||
* @param array $item_source The item source descriptor array
|
||||
*/
|
||||
$paths = apply_filters( 'as3cf_remove_source_files_from_provider', $paths, $as3cf_item, $as3cf_item->get_item_source_array() );
|
||||
$paths = array_unique( $paths );
|
||||
|
||||
// Remove local source paths that other items may have offloaded.
|
||||
$paths = $as3cf_item->remove_duplicate_paths( $as3cf_item, $paths );
|
||||
|
||||
// Nothing to do, shortcut out.
|
||||
if ( empty( $paths ) ) {
|
||||
return $manifest;
|
||||
}
|
||||
|
||||
if ( empty( $options['offloaded_files'] ) ) {
|
||||
foreach ( $paths as $object_key => $path ) {
|
||||
$manifest->objects[] = array(
|
||||
'Key' => $as3cf_item->provider_key( $object_key ),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
foreach ( $paths as $filename => $path ) {
|
||||
$manifest->objects[] = array(
|
||||
'Key' => $as3cf_item->provider_key_for_filename( $filename, $options['offloaded_files'][ $filename ]['is_private'] ),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete provider objects described in the manifest object array
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param Manifest $manifest
|
||||
* @param array $options
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
protected function handle_item( Item $as3cf_item, Manifest $manifest, array $options ) {
|
||||
// This test is "late" so that we don't raise the error if there is nothing to remove.
|
||||
// If the provider of this item is different from what's currently configured,
|
||||
// we'll return an error.
|
||||
$current_provider = $this->as3cf->get_storage_provider();
|
||||
if ( ! is_null( $current_provider ) && $current_provider::get_provider_key_name() !== $as3cf_item->provider() ) {
|
||||
$error_msg = sprintf(
|
||||
__( '%1$s with ID %2$d is offloaded to a different provider than currently configured', 'amazon-s3-and-cloudfront' ),
|
||||
$this->as3cf->get_source_type_name( $as3cf_item->source_type() ),
|
||||
$as3cf_item->source_id()
|
||||
);
|
||||
|
||||
return $this->return_handler_error( $error_msg );
|
||||
}
|
||||
|
||||
$chunks = array_chunk( $manifest->objects, 1000 );
|
||||
$region = $as3cf_item->region();
|
||||
$bucket = $as3cf_item->bucket();
|
||||
|
||||
try {
|
||||
foreach ( $chunks as $chunk ) {
|
||||
$this->as3cf->get_provider_client( $region )->delete_objects( array(
|
||||
'Bucket' => $bucket,
|
||||
'Objects' => $chunk,
|
||||
) );
|
||||
}
|
||||
} catch ( Exception $e ) {
|
||||
$error_msg = sprintf( __( 'Error removing files from bucket: %s', 'amazon-s3-and-cloudfront' ), $e->getMessage() );
|
||||
|
||||
return $this->return_handler_error( $error_msg );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform post handle tasks.
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param Manifest $manifest
|
||||
* @param array $options
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function post_handle( Item $as3cf_item, Manifest $manifest, array $options ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
434
classes/items/upload-handler.php
Normal file
434
classes/items/upload-handler.php
Normal file
@@ -0,0 +1,434 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Items;
|
||||
|
||||
use AS3CF_Error;
|
||||
use AS3CF_Utils;
|
||||
use DeliciousBrains\WP_Offload_Media\Providers\Storage\Storage_Provider;
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
|
||||
class Upload_Handler extends Item_Handler {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $item_handler_key = 'upload';
|
||||
|
||||
/**
|
||||
* Keep track of individual files we've already attempted to upload
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $attempted_upload = array();
|
||||
|
||||
/**
|
||||
* The default options that should be used if none supplied.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function default_options() {
|
||||
return array(
|
||||
'offloaded_files' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare item for uploading by running filters, updating
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param array $options
|
||||
*
|
||||
* @return Manifest|WP_Error
|
||||
*/
|
||||
protected function pre_handle( Item $as3cf_item, array $options ) {
|
||||
$manifest = new Manifest();
|
||||
$source_type_name = $this->as3cf->get_source_type_name( $as3cf_item->source_type() );
|
||||
$primary_key = Item::primary_object_key();
|
||||
|
||||
// Check for valid file path before attempting upload
|
||||
if ( empty( $as3cf_item->source_path() ) ) {
|
||||
$error_msg = sprintf( __( '%1$s with id %2$d does not have a valid file path', 'amazon-s3-and-cloudfront' ), $source_type_name, $as3cf_item->source_id() );
|
||||
|
||||
return $this->return_handler_error( $error_msg );
|
||||
}
|
||||
|
||||
// Ensure path is a string
|
||||
if ( ! is_string( $as3cf_item->source_path() ) ) {
|
||||
$error_msg = sprintf( __( '%1$s with id %2$d. Provided path is not a string', 'amazon-s3-and-cloudfront' ), $source_type_name, $as3cf_item->source_id() );
|
||||
|
||||
return $this->return_handler_error( $error_msg );
|
||||
}
|
||||
|
||||
// Ensure primary source file exists for new offload.
|
||||
if ( empty( $as3cf_item->id() ) && ! file_exists( $as3cf_item->full_source_path( $primary_key ) ) ) {
|
||||
$error_msg = sprintf( __( 'Primary file %1$s for %2$s with id %3$s does not exist', 'amazon-s3-and-cloudfront' ), $as3cf_item->full_source_path( $primary_key ), $source_type_name, $as3cf_item->source_id() );
|
||||
|
||||
return $this->return_handler_error( $error_msg );
|
||||
}
|
||||
|
||||
// Get primary file's stats.
|
||||
$file_name = wp_basename( $as3cf_item->source_path() );
|
||||
$file_type = wp_check_filetype_and_ext( $as3cf_item->full_source_path(), $file_name );
|
||||
$allowed_types = $this->as3cf->get_allowed_mime_types();
|
||||
|
||||
// check mime type of file is in allowed provider mime types
|
||||
if ( ! in_array( $file_type['type'], $allowed_types, true ) ) {
|
||||
$error_msg = sprintf( __( 'Mime type "%1$s" is not allowed (%2$s with id %3$s)', 'amazon-s3-and-cloudfront' ), $file_type['type'], $source_type_name, $as3cf_item->source_id() );
|
||||
|
||||
return $this->return_handler_error( $error_msg );
|
||||
}
|
||||
|
||||
$default_acl = $this->as3cf->get_storage_provider()->get_default_acl();
|
||||
$private_acl = $this->as3cf->get_storage_provider()->get_private_acl();
|
||||
|
||||
foreach ( $as3cf_item->objects() as $object_key => $object ) {
|
||||
// Avoid attempting uploading to an item that doesn't have the primary file in place.
|
||||
if ( $primary_key !== $object_key && empty( $as3cf_item->id() ) && ! isset( $manifest->objects[ $primary_key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$source_path = $as3cf_item->full_source_path( $object_key );
|
||||
|
||||
// If the file has already been offloaded,
|
||||
// don't try and (fail to) re-offload if the file isn't available.
|
||||
if ( $this->in_offloaded_files( $object['source_file'], $options ) && ! file_exists( $source_path ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/**
|
||||
* This filter allows you to change the public/private status of an individual file associated
|
||||
* with an uploaded item before it's uploaded to the provider.
|
||||
*
|
||||
* @param bool $is_private Should the object be private?
|
||||
* @param string $object_key A unique file identifier for a composite item, e.g. image's "size" such as full, small, medium, large.
|
||||
* @param Item $as3cf_item The item being uploaded.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
$is_private = apply_filters( 'as3cf_upload_object_key_as_private', $as3cf_item->is_private( $object_key ), $object_key, $as3cf_item );
|
||||
$as3cf_item->set_is_private( $is_private, $object_key );
|
||||
|
||||
$object_acl = $as3cf_item->is_private( $object_key ) ? $private_acl : $default_acl;
|
||||
|
||||
$args = array(
|
||||
'Bucket' => $as3cf_item->bucket(),
|
||||
'Key' => $as3cf_item->path( $object_key ),
|
||||
'SourceFile' => $source_path,
|
||||
'ContentType' => AS3CF_Utils::get_mime_type( $object['source_file'] ),
|
||||
'CacheControl' => 'max-age=31536000',
|
||||
);
|
||||
|
||||
// Only set ACL if actually required, some storage provider and bucket settings disable changing ACL.
|
||||
if ( ! empty( $object_acl ) && $this->as3cf->use_acl_for_intermediate_size( 0, $object_key, $as3cf_item->bucket(), $as3cf_item ) ) {
|
||||
$args['ACL'] = $object_acl;
|
||||
}
|
||||
|
||||
// TODO: Remove GZIP functionality.
|
||||
// Handle gzip on supported items
|
||||
if (
|
||||
$this->should_gzip_file( $source_path, $as3cf_item->source_type() ) &&
|
||||
false !== ( $gzip_body = gzencode( file_get_contents( $source_path ) ) )
|
||||
) {
|
||||
unset( $args['SourceFile'] );
|
||||
|
||||
$args['Body'] = $gzip_body;
|
||||
$args['ContentEncoding'] = 'gzip';
|
||||
}
|
||||
|
||||
$args = Storage_Provider::filter_object_meta( $args, $as3cf_item, $object_key );
|
||||
|
||||
// If the bucket is changed by the filter while processing the primary object,
|
||||
// we should try and use that bucket for the item.
|
||||
// If the bucket name is invalid, revert to configured bucket but log it.
|
||||
// We don't abort here as ephemeral filesystems need to be accounted for,
|
||||
// and the configured bucket is at least known to work.
|
||||
if ( $primary_key === $object_key && $as3cf_item->bucket() !== $args['Bucket'] && empty( $as3cf_item->id() ) ) {
|
||||
$bucket = $this->as3cf->check_bucket( $args['Bucket'] );
|
||||
|
||||
if ( $bucket ) {
|
||||
$region = $this->as3cf->get_bucket_region( $bucket );
|
||||
|
||||
if ( is_wp_error( $region ) ) {
|
||||
unset( $region );
|
||||
}
|
||||
}
|
||||
|
||||
if ( empty( $bucket ) || empty( $region ) ) {
|
||||
$mesg = sprintf(
|
||||
__( 'Bucket name "%1$s" is invalid, using "%2$s" instead.', 'amazon-s3-and-cloudfront' ),
|
||||
$args['Bucket'],
|
||||
$as3cf_item->bucket()
|
||||
);
|
||||
AS3CF_Error::log( $mesg );
|
||||
$args['Bucket'] = $as3cf_item->bucket();
|
||||
} else {
|
||||
$args['Bucket'] = $bucket;
|
||||
$as3cf_item->set_bucket( $bucket );
|
||||
$as3cf_item->set_region( $region );
|
||||
}
|
||||
|
||||
unset( $bucket, $region );
|
||||
} elseif ( $primary_key === $object_key && $as3cf_item->bucket() !== $args['Bucket'] && ! empty( $as3cf_item->id() ) ) {
|
||||
$args['Bucket'] = $as3cf_item->bucket();
|
||||
AS3CF_Error::log( __( 'The bucket may not be changed via filters for a previously offloaded item.', 'amazon-s3-and-cloudfront' ) );
|
||||
} elseif ( $primary_key !== $object_key && $as3cf_item->bucket() !== $args['Bucket'] ) {
|
||||
$args['Bucket'] = $as3cf_item->bucket();
|
||||
}
|
||||
|
||||
// If the Key has been changed for the primary object key, then that should be reflected in the item.
|
||||
if ( $primary_key === $object_key && $as3cf_item->path( $object_key ) !== $args['Key'] && empty( $as3cf_item->id() ) ) {
|
||||
$prefix = AS3CF_Utils::trailingslash_prefix( dirname( $args['Key'] ) );
|
||||
|
||||
if ( $prefix === '.' ) {
|
||||
$prefix = '';
|
||||
}
|
||||
|
||||
$as3cf_item->update_path_prefix( $prefix );
|
||||
|
||||
// If the filter tried to use a different filename too, log it.
|
||||
if ( wp_basename( $args['Key'] ) !== wp_basename( $as3cf_item->path( $object_key ) ) ) {
|
||||
$mesg = sprintf(
|
||||
__( 'The offloaded filename must not be changed, "%1$s" has been used instead of "%2$s".', 'amazon-s3-and-cloudfront' ),
|
||||
wp_basename( $as3cf_item->path( $object_key ) ),
|
||||
wp_basename( $args['Key'] )
|
||||
);
|
||||
AS3CF_Error::log( $mesg );
|
||||
}
|
||||
} elseif ( $primary_key === $object_key && $as3cf_item->path( $object_key ) !== $args['Key'] && ! empty( $as3cf_item->id() ) ) {
|
||||
$args['Key'] = $as3cf_item->path( $object_key );
|
||||
AS3CF_Error::log( __( 'The key may not be changed via filters for a previously offloaded item.', 'amazon-s3-and-cloudfront' ) );
|
||||
} elseif ( $primary_key !== $object_key && $as3cf_item->path( $object_key ) !== $args['Key'] ) {
|
||||
$args['Key'] = $as3cf_item->path( $object_key );
|
||||
}
|
||||
|
||||
// If ACL has been set, does the object's is_private need updating?
|
||||
$is_private = ! empty( $args['ACL'] ) && $private_acl === $args['ACL'] || $as3cf_item->is_private( $object_key );
|
||||
$as3cf_item->set_is_private( $is_private, $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'] ) && ! $this->as3cf->use_acl_for_intermediate_size( 0, $object_key, $as3cf_item->bucket(), $as3cf_item ) ) {
|
||||
unset( $args['ACL'] );
|
||||
}
|
||||
|
||||
// Adjust the actual Key to add the private prefix before uploading.
|
||||
if ( $as3cf_item->is_private( $object_key ) ) {
|
||||
$args['Key'] = $as3cf_item->provider_key( $object_key );
|
||||
}
|
||||
|
||||
// If we've already attempted to offload this source file, leave it out of the manifest.
|
||||
if ( in_array( md5( serialize( $args ) ), $this->attempted_upload ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $primary_key === $object_key ) {
|
||||
/**
|
||||
* Actions fires when an Item's primary file might be offloaded.
|
||||
*
|
||||
* This action gives notice that an Item is being processed for upload to a bucket,
|
||||
* and the given arguments represent the primary file's potential offload location.
|
||||
* However, if the current process is for picking up extra files associated with the item,
|
||||
* the indicated primary file may not actually be offloaded if it does not exist
|
||||
* on the server but has already been offloaded.
|
||||
*
|
||||
* @param Item $as3cf_item The Item whose files are being offloaded.
|
||||
* @param array $args The arguments that could be used to offload the primary file.
|
||||
*/
|
||||
do_action( 'as3cf_pre_upload_object', $as3cf_item, $args );
|
||||
}
|
||||
|
||||
$manifest->objects[ $object_key ]['args'] = $args;
|
||||
}
|
||||
|
||||
return $manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload item files to remote storage provider
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param Manifest $manifest
|
||||
* @param array $options
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
protected function handle_item( Item $as3cf_item, Manifest $manifest, array $options ) {
|
||||
$source_type_name = $this->as3cf->get_source_type_name( $as3cf_item->source_type() );
|
||||
|
||||
try {
|
||||
$provider_client = $this->as3cf->get_provider_client( $as3cf_item->region() );
|
||||
} catch ( Exception $e ) {
|
||||
return $this->return_handler_error( $e->getMessage() );
|
||||
}
|
||||
|
||||
foreach ( $manifest->objects as $object_key => &$object ) {
|
||||
$args = $object['args'];
|
||||
|
||||
$object['upload_result'] = array(
|
||||
'status' => null,
|
||||
'message' => null,
|
||||
);
|
||||
|
||||
if ( ! file_exists( $args['SourceFile'] ) ) {
|
||||
$error_msg = sprintf( __( 'File %1$s does not exist (%2$s with id %3$s)', 'amazon-s3-and-cloudfront' ), $args['SourceFile'], $source_type_name, $as3cf_item->source_id() );
|
||||
|
||||
$object['upload_result']['status'] = self::STATUS_FAILED;
|
||||
$object['upload_result']['message'] = $error_msg;
|
||||
|
||||
// If the missing source file is the primary file, abort the whole process.
|
||||
if ( Item::primary_object_key() === $object_key ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$this->attempted_upload[] = md5( serialize( $args ) );
|
||||
|
||||
// Try to do the upload
|
||||
try {
|
||||
$provider_client->upload_object( $args );
|
||||
|
||||
$object['upload_result']['status'] = self::STATUS_OK;
|
||||
} catch ( Exception $e ) {
|
||||
$error_msg = sprintf( __( 'Error offloading %1$s to provider: %2$s (%3$s with id %4$s)', 'amazon-s3-and-cloudfront' ), $args['SourceFile'], $e->getMessage(), $source_type_name, $as3cf_item->source_id() );
|
||||
|
||||
$object['upload_result']['status'] = self::STATUS_FAILED;
|
||||
$object['upload_result']['message'] = $error_msg;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle local housekeeping after uploads.
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param Manifest $manifest
|
||||
* @param array $options
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
protected function post_handle( Item $as3cf_item, Manifest $manifest, array $options ) {
|
||||
$item_objects = $as3cf_item->objects();
|
||||
$errors = new WP_Error();
|
||||
$i = 1;
|
||||
|
||||
// Reconcile the Item's objects with their manifest status.
|
||||
foreach ( $item_objects as $object_key => $object ) {
|
||||
// If there was no attempt made to offload the file,
|
||||
// then remove it from list of offloaded objects.
|
||||
// However, if the source file has previously been offloaded,
|
||||
// we should just skip any further processing of it
|
||||
// as the associated objects are still offloaded.
|
||||
if ( ! isset( $manifest->objects[ $object_key ]['upload_result']['status'] ) ) {
|
||||
if ( empty( $options['offloaded_files'][ $object['source_file'] ] ) ) {
|
||||
unset( $item_objects[ $object_key ] );
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// If the upload didn't succeed, we need to remove the object/size from the item.
|
||||
// However, if the source file has previously been offloaded, we should just log the error.
|
||||
if ( $manifest->objects[ $object_key ]['upload_result']['status'] !== self::STATUS_OK ) {
|
||||
if ( empty( $options['offloaded_files'][ $object['source_file'] ] ) ) {
|
||||
unset( $item_objects[ $object_key ] );
|
||||
}
|
||||
$errors->add( 'upload-object-' . ( $i++ ), $manifest->objects[ $object_key ]['upload_result']['message'] );
|
||||
}
|
||||
}
|
||||
|
||||
// Set the potentially changed list of offloaded objects.
|
||||
$as3cf_item->set_objects( $item_objects );
|
||||
|
||||
// Only save if we have the primary file uploaded.
|
||||
if ( isset( $item_objects[ Item::primary_object_key() ] ) ) {
|
||||
$as3cf_item->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires action after uploading finishes
|
||||
*
|
||||
* @param Item $as3cf_item The item that was just uploaded
|
||||
*/
|
||||
do_action( 'as3cf_post_upload_item', $as3cf_item );
|
||||
|
||||
if ( count( $errors->get_error_codes() ) ) {
|
||||
return $errors;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should gzip file
|
||||
*
|
||||
* @param string $file_path
|
||||
* @param string $source_type
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function should_gzip_file( $file_path, $source_type ) {
|
||||
$file_type = wp_check_filetype_and_ext( $file_path, $file_path );
|
||||
$mimes = $this->get_mime_types_to_gzip( $source_type );
|
||||
|
||||
if ( in_array( $file_type, $mimes ) && is_readable( $file_path ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get mime types to gzip
|
||||
*
|
||||
* @param string $source_type
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_mime_types_to_gzip( $source_type ) {
|
||||
/**
|
||||
* Return array of mime types that needs to be gzipped before upload
|
||||
*
|
||||
* @param array $mime_types The array of mime types
|
||||
* @param bool $media_library If the uploaded file is part of the media library
|
||||
* @param string $source_type The source type of the uploaded item
|
||||
*/
|
||||
return apply_filters(
|
||||
'as3cf_gzip_mime_types',
|
||||
array(
|
||||
'css' => 'text/css',
|
||||
'eot' => 'application/vnd.ms-fontobject',
|
||||
'html' => 'text/html',
|
||||
'ico' => 'image/x-icon',
|
||||
'js' => 'application/javascript',
|
||||
'json' => 'application/json',
|
||||
'otf' => 'application/x-font-opentype',
|
||||
'rss' => 'application/rss+xml',
|
||||
'svg' => 'image/svg+xml',
|
||||
'ttf' => 'application/x-font-ttf',
|
||||
'woff' => 'application/font-woff',
|
||||
'woff2' => 'application/font-woff2',
|
||||
'xml' => 'application/xml',
|
||||
),
|
||||
'media_library' === $source_type,
|
||||
$source_type
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Has the given file name already been offloaded?
|
||||
*
|
||||
* @param string $filename
|
||||
* @param array $options
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function in_offloaded_files( $filename, $options ) {
|
||||
if ( empty( $options['offloaded_files'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return array_key_exists( $filename, $options['offloaded_files'] );
|
||||
}
|
||||
}
|
||||
1012
classes/pro/amazon-s3-and-cloudfront-pro.php
Normal file
1012
classes/pro/amazon-s3-and-cloudfront-pro.php
Normal file
File diff suppressed because it is too large
Load Diff
11
classes/pro/api/api.php
Normal file
11
classes/pro/api/api.php
Normal file
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\API;
|
||||
|
||||
use Amazon_S3_And_CloudFront_Pro;
|
||||
use DeliciousBrains\WP_Offload_Media\API\API as Lite_API;
|
||||
|
||||
abstract class API extends Lite_API {
|
||||
/** @var Amazon_S3_And_CloudFront_Pro */
|
||||
protected $as3cf;
|
||||
}
|
||||
114
classes/pro/api/v1/licences.php
Normal file
114
classes/pro/api/v1/licences.php
Normal file
@@ -0,0 +1,114 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\API\V1;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\API\API;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
class Licences extends API {
|
||||
/** @var int */
|
||||
protected static $version = 1;
|
||||
|
||||
/** @var string */
|
||||
protected static $name = 'licences';
|
||||
|
||||
/**
|
||||
* Register REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route(),
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'get_licences' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route(),
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'callback' => array( $this, 'post_licences' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route(),
|
||||
array(
|
||||
'methods' => 'DELETE',
|
||||
'callback' => array( $this, 'delete_licences' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a REST GET request to retrieve licence info.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response|mixed
|
||||
*/
|
||||
public function get_licences( WP_REST_Request $request ) {
|
||||
$data = $request->get_json_params();
|
||||
|
||||
$force = ! empty( $data['force'] );
|
||||
|
||||
return $this->rest_ensure_response( 'get', static::name(), array(
|
||||
'licences' => $this->as3cf->get_licences( $force ),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a REST POST request to activate supplied licence and return resultant licence info.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response|mixed
|
||||
*/
|
||||
public function post_licences( WP_REST_Request $request ) {
|
||||
$data = $request->get_json_params();
|
||||
$licence_key = empty( $data['licence'] ) ? '' : $data['licence'];
|
||||
$result = $this->as3cf->activate_licence( $licence_key );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $this->rest_ensure_response( 'post', static::name(), $result );
|
||||
}
|
||||
|
||||
return $this->rest_ensure_response( 'post', static::name(), array(
|
||||
'licences' => $this->as3cf->get_licences(),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a REST DELETE request to deactivate current licence and return resultant licence info.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response|mixed
|
||||
*/
|
||||
public function delete_licences( WP_REST_Request $request ) {
|
||||
if ( $this->as3cf->is_licence_constant() ) {
|
||||
return $this->rest_ensure_response( 'delete', static::name(),
|
||||
new WP_Error(
|
||||
'licence-constant',
|
||||
__( 'Your licence key is currently defined via a constant and must be removed manually.', 'amazon-s3-and-cloudfront' )
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// We currently have one licence applied to a plugin install.
|
||||
$this->as3cf->remove_licence();
|
||||
|
||||
return $this->rest_ensure_response( 'delete', static::name(), array(
|
||||
'licences' => $this->as3cf->get_licences(),
|
||||
) );
|
||||
}
|
||||
}
|
||||
153
classes/pro/api/v1/tools.php
Normal file
153
classes/pro/api/v1/tools.php
Normal file
@@ -0,0 +1,153 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\API\V1;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\API\API;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
class Tools extends API {
|
||||
/** @var int */
|
||||
protected static $version = 1;
|
||||
|
||||
/** @var string */
|
||||
protected static $name = 'tools';
|
||||
|
||||
/**
|
||||
* Register REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route(),
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'get_tools' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route(),
|
||||
array(
|
||||
'methods' => 'PUT',
|
||||
'callback' => array( $this, 'put_tools' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route(),
|
||||
array(
|
||||
'methods' => 'DELETE',
|
||||
'callback' => array( $this, 'delete_tools' ),
|
||||
'permission_callback' => array( $this, 'check_permissions' ),
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a REST GET request and returns the current tools.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response|mixed
|
||||
*/
|
||||
public function get_tools( WP_REST_Request $request ) {
|
||||
return $this->rest_ensure_response( 'get', static::name(), array(
|
||||
'tools' => $this->as3cf->get_tools_info(),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a REST PUT request to perform an action on a tool and returns confirmation of whether it was ok or not.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response|mixed
|
||||
*/
|
||||
public function put_tools( WP_REST_Request $request ) {
|
||||
$data = $request->get_json_params();
|
||||
|
||||
if ( empty( $data['id'] ) ) {
|
||||
return $this->rest_ensure_response( 'put', static::name(),
|
||||
new WP_Error( 'missing-tool-id', __( 'Tool ID not supplied.', 'amazon-s3-and-cloudfront' ) )
|
||||
);
|
||||
}
|
||||
|
||||
if ( empty( $data['action'] ) ) {
|
||||
return $this->rest_ensure_response( 'put', static::name(),
|
||||
new WP_Error( 'missing-tool-action', __( 'Action not supplied.', 'amazon-s3-and-cloudfront' ) )
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! in_array( $data['action'], array( 'start', 'cancel', 'pause_resume' ) ) ) {
|
||||
return $this->rest_ensure_response( 'put', static::name(),
|
||||
new WP_Error( 'invalid-tool-action', __( 'Invalid tool action supplied.', 'amazon-s3-and-cloudfront' ) )
|
||||
);
|
||||
}
|
||||
|
||||
$result = $this->as3cf->perform_tool_action( $data['id'], $data['action'] );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $this->rest_ensure_response( 'put', static::name(), $result );
|
||||
}
|
||||
|
||||
return $this->rest_ensure_response( 'put', static::name(), array(
|
||||
'ok' => $result,
|
||||
'tools' => $this->as3cf->get_tools_info(),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a REST DELETE request to dismiss a tool's errors.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response|mixed
|
||||
*/
|
||||
public function delete_tools( WP_REST_Request $request ) {
|
||||
$data = $request->get_json_params();
|
||||
|
||||
if ( empty( $data['id'] ) ) {
|
||||
return $this->rest_ensure_response( 'delete', static::name(),
|
||||
new WP_Error( 'missing-tool-id', __( 'Tool ID not supplied.', 'amazon-s3-and-cloudfront' ) )
|
||||
);
|
||||
}
|
||||
|
||||
if ( empty( $data['blog_id'] ) ) {
|
||||
return $this->rest_ensure_response( 'delete', static::name(),
|
||||
new WP_Error( 'missing-blog-id', __( 'Blog ID not supplied.', 'amazon-s3-and-cloudfront' ) )
|
||||
);
|
||||
}
|
||||
|
||||
if ( empty( $data['source_type'] ) ) {
|
||||
return $this->rest_ensure_response( 'delete', static::name(),
|
||||
new WP_Error( 'missing-source-type', __( 'Source Type not supplied.', 'amazon-s3-and-cloudfront' ) )
|
||||
);
|
||||
}
|
||||
|
||||
if ( empty( $data['source_id'] ) ) {
|
||||
return $this->rest_ensure_response( 'delete', static::name(),
|
||||
new WP_Error( 'missing-source-id', __( 'Source ID not supplied.', 'amazon-s3-and-cloudfront' ) )
|
||||
);
|
||||
}
|
||||
|
||||
if ( ! isset( $data['errors'] ) ) {
|
||||
$data['errors'] = 'all';
|
||||
}
|
||||
|
||||
$result = $this->as3cf->dismiss_tool_errors( $data['id'], $data['blog_id'], $data['source_type'], $data['source_id'], $data['errors'] );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $this->rest_ensure_response( 'delete', static::name(), $result );
|
||||
}
|
||||
|
||||
return $this->rest_ensure_response( 'delete', static::name(), array(
|
||||
'ok' => true,
|
||||
) );
|
||||
}
|
||||
}
|
||||
140
classes/pro/as3cf-async-request.php
Normal file
140
classes/pro/as3cf-async-request.php
Normal file
@@ -0,0 +1,140 @@
|
||||
<?php
|
||||
|
||||
abstract class AS3CF_Async_Request {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $prefix = 'as3cf';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'async_request';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $identifier;
|
||||
|
||||
/**
|
||||
* @var Amazon_S3_And_CloudFront_Pro
|
||||
*/
|
||||
protected $as3cf;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $data = array();
|
||||
|
||||
/**
|
||||
* Initiate new async request
|
||||
*
|
||||
* @param Amazon_S3_And_CloudFront $as3cf Instance of calling class
|
||||
*/
|
||||
public function __construct( $as3cf ) {
|
||||
$this->as3cf = $as3cf;
|
||||
$this->identifier = $this->prefix . '_' . $this->action;
|
||||
|
||||
add_action( 'wp_ajax_' . $this->identifier, array( $this, 'maybe_handle' ) );
|
||||
add_action( 'wp_ajax_nopriv_' . $this->identifier, array( $this, 'maybe_handle' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set data used during the request
|
||||
*
|
||||
* @param array $data
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function data( $data ) {
|
||||
$this->data = $data;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch the async request
|
||||
*
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public function dispatch() {
|
||||
$url = add_query_arg( $this->get_query_args(), $this->get_query_url() );
|
||||
$args = $this->get_post_args();
|
||||
|
||||
return wp_remote_post( esc_url_raw( $url ), $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get query args
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_query_args() {
|
||||
if ( property_exists( $this, 'query_args' ) ) {
|
||||
return $this->query_args;
|
||||
}
|
||||
|
||||
return array(
|
||||
'action' => $this->identifier,
|
||||
'nonce' => wp_create_nonce( $this->identifier ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get query URL
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_query_url() {
|
||||
if ( property_exists( $this, 'query_url' ) ) {
|
||||
return $this->query_url;
|
||||
}
|
||||
|
||||
return admin_url( 'admin-ajax.php' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get post args
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_post_args() {
|
||||
if ( property_exists( $this, 'post_args' ) ) {
|
||||
return $this->post_args;
|
||||
}
|
||||
|
||||
return array(
|
||||
'timeout' => 0.01,
|
||||
'blocking' => false,
|
||||
'cookies' => $_COOKIE, // Passing cookies ensures request is performed as initiating user
|
||||
'body' => $this->data,
|
||||
'sslverify' => apply_filters( 'https_local_ssl_verify', false ), // Local requests, fine to pass false
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe handle
|
||||
*
|
||||
* Check for correct nonce and pass to handler.
|
||||
*/
|
||||
public function maybe_handle() {
|
||||
// Don't lock up other requests while processing
|
||||
session_write_close();
|
||||
|
||||
check_ajax_referer( $this->identifier, 'nonce' );
|
||||
|
||||
$this->handle();
|
||||
|
||||
wp_die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle
|
||||
*
|
||||
* Override this method to perform any actions required
|
||||
* during the async request.
|
||||
*/
|
||||
abstract protected function handle();
|
||||
|
||||
}
|
||||
572
classes/pro/as3cf-background-process.php
Normal file
572
classes/pro/as3cf-background-process.php
Normal file
@@ -0,0 +1,572 @@
|
||||
<?php
|
||||
|
||||
abstract class AS3CF_Background_Process extends AS3CF_Async_Request {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action = 'background-process';
|
||||
|
||||
/**
|
||||
* Start time of current process
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $start_time = 0;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
const STATUS_CANCELLED = 1;
|
||||
|
||||
/**
|
||||
* @var int;
|
||||
*/
|
||||
const STATUS_PAUSED = 2;
|
||||
|
||||
/**
|
||||
* Initiate new background process
|
||||
*
|
||||
* @param Amazon_S3_And_CloudFront $as3cf Instance of calling class
|
||||
*/
|
||||
public function __construct( $as3cf ) {
|
||||
parent::__construct( $as3cf );
|
||||
|
||||
add_action( $this->identifier . '_cron', array( $this, 'handle_cron_healthcheck' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatch
|
||||
*/
|
||||
public function dispatch() {
|
||||
$this->schedule_cron_healthcheck();
|
||||
|
||||
// Perform remote post
|
||||
parent::dispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Push to queue
|
||||
*
|
||||
* @param mixed $data
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function push_to_queue( $data ) {
|
||||
$this->data[] = $data;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Save queue
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function save() {
|
||||
$key = $this->generate_key( 'batch' );
|
||||
|
||||
if ( ! empty( $this->data ) ) {
|
||||
update_site_option( $key, $this->data );
|
||||
}
|
||||
|
||||
// Clean out data so that new data isn't prepended with closed session's data.
|
||||
$this->data = array();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update queue
|
||||
*
|
||||
* @param string $key
|
||||
* @param array $data
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function update( $key, $data ) {
|
||||
if ( ! empty( $data ) ) {
|
||||
update_site_option( $key, $data );
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete job.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function delete( $key ) {
|
||||
delete_site_option( $key );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete entire job queue.
|
||||
*/
|
||||
public function delete_all() {
|
||||
$batches = $this->get_batches();
|
||||
|
||||
foreach ( $batches as $batch ) {
|
||||
$this->delete( $batch->key );
|
||||
}
|
||||
|
||||
delete_site_option( $this->get_status_key() );
|
||||
|
||||
$this->cancelled();
|
||||
do_action( $this->identifier . '_cancelled' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel job on next batch.
|
||||
*/
|
||||
public function cancel() {
|
||||
update_site_option( $this->get_status_key(), self::STATUS_CANCELLED );
|
||||
|
||||
// Just in case the job was paused at the time.
|
||||
$this->dispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* Has the process been cancelled?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_cancelled() {
|
||||
$status = get_site_option( $this->get_status_key(), 0 );
|
||||
|
||||
if ( absint( $status ) === self::STATUS_CANCELLED ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been cancelled.
|
||||
*/
|
||||
abstract protected function cancelled();
|
||||
|
||||
/**
|
||||
* Pause job on next batch.
|
||||
*/
|
||||
public function pause() {
|
||||
update_site_option( $this->get_status_key(), self::STATUS_PAUSED );
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the job paused?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_paused() {
|
||||
$status = get_site_option( $this->get_status_key(), 0 );
|
||||
|
||||
if ( absint( $status ) === self::STATUS_PAUSED ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been paused.
|
||||
*/
|
||||
abstract protected function paused();
|
||||
|
||||
/**
|
||||
* Resume job.
|
||||
*/
|
||||
public function resume() {
|
||||
delete_site_option( $this->get_status_key() );
|
||||
|
||||
$this->schedule_cron_healthcheck();
|
||||
$this->dispatch();
|
||||
$this->resumed();
|
||||
do_action( $this->identifier . '_resumed' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has been resumed.
|
||||
*/
|
||||
abstract protected function resumed();
|
||||
|
||||
/**
|
||||
* Generate key
|
||||
*
|
||||
* Generates a unique key based on microtime. Queue items are
|
||||
* given a unique key so that they can be merged upon save.
|
||||
*
|
||||
* @param string $key
|
||||
* @param int $length
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function generate_key( $key = '', $length = 64 ) {
|
||||
$unique = md5( microtime() . rand() );
|
||||
$prepend = $this->identifier . '_' . $key . '_';
|
||||
|
||||
return substr( $prepend . $unique, 0, $length );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the status key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_status_key() {
|
||||
return $this->identifier . '_status';
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe process queue
|
||||
*
|
||||
* Checks whether data exists within the queue and that
|
||||
* the process is not already running.
|
||||
*/
|
||||
public function maybe_handle() {
|
||||
// Don't lock up other requests while processing
|
||||
session_write_close();
|
||||
|
||||
if ( $this->is_process_running() ) {
|
||||
// Background process already running
|
||||
wp_die();
|
||||
}
|
||||
|
||||
if ( $this->is_cancelled() ) {
|
||||
$this->clear_cron_healthcheck();
|
||||
|
||||
$this->delete_all();
|
||||
|
||||
wp_die();
|
||||
}
|
||||
|
||||
if ( $this->is_paused() ) {
|
||||
$this->clear_cron_healthcheck();
|
||||
|
||||
$this->paused();
|
||||
do_action( $this->identifier . '_paused' );
|
||||
|
||||
wp_die();
|
||||
}
|
||||
|
||||
if ( $this->is_queue_empty() ) {
|
||||
// No data to process
|
||||
wp_die();
|
||||
}
|
||||
|
||||
check_ajax_referer( $this->identifier, 'nonce' );
|
||||
|
||||
$this->handle();
|
||||
|
||||
wp_die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is queue empty
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_queue_empty() {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->options;
|
||||
$column = 'option_name';
|
||||
|
||||
if ( is_multisite() ) {
|
||||
$table = $wpdb->sitemeta;
|
||||
$column = 'meta_key';
|
||||
}
|
||||
|
||||
$key = $this->identifier . '_batch_%';
|
||||
|
||||
$count = $wpdb->get_var( $wpdb->prepare( "
|
||||
SELECT COUNT(*)
|
||||
FROM {$table}
|
||||
WHERE {$column} LIKE %s
|
||||
", $key ) );
|
||||
|
||||
return ( $count > 0 ) ? false : true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is process running
|
||||
*
|
||||
* Check whether the current process is already running
|
||||
* in a background process.
|
||||
*/
|
||||
public function is_process_running() {
|
||||
if ( get_site_transient( $this->identifier . '_process_lock' ) ) {
|
||||
// Process already running
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock process
|
||||
*
|
||||
* Lock the process so that multiple instances can't run simultaneously.
|
||||
* Override if applicable, but the duration should be greater than that
|
||||
* defined in the time_exceeded() method.
|
||||
*/
|
||||
protected function lock_process() {
|
||||
$this->start_time = time(); // Set start time of current process
|
||||
|
||||
$lock_duration = ( property_exists( $this, 'queue_lock_time' ) ) ? $this->queue_lock_time : 60; // 1 minute
|
||||
$lock_duration = apply_filters( $this->identifier . '_queue_lock_time', $lock_duration );
|
||||
|
||||
set_site_transient( $this->identifier . '_process_lock', microtime(), $lock_duration );
|
||||
}
|
||||
|
||||
/**
|
||||
* Unlock process
|
||||
*
|
||||
* Unlock the process so that other instances can spawn.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
protected function unlock_process() {
|
||||
delete_site_transient( $this->identifier . '_process_lock' );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get batch
|
||||
*
|
||||
* @return stdClass Return the first batch from the queue
|
||||
*/
|
||||
protected function get_batch() {
|
||||
return array_reduce(
|
||||
$this->get_batches( 1 ),
|
||||
function ( $carry, $batch ) {
|
||||
return $batch;
|
||||
},
|
||||
array()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get batches
|
||||
*
|
||||
* @param int $limit Number of batches to return, defaults to all.
|
||||
*
|
||||
* @return array of stdClass
|
||||
*/
|
||||
public function get_batches( $limit = 0 ) {
|
||||
global $wpdb;
|
||||
|
||||
if ( empty( $limit ) || ! is_int( $limit ) ) {
|
||||
$limit = 0;
|
||||
}
|
||||
|
||||
$table = $wpdb->options;
|
||||
$column = 'option_name';
|
||||
$key_column = 'option_id';
|
||||
$value_column = 'option_value';
|
||||
|
||||
if ( is_multisite() ) {
|
||||
$table = $wpdb->sitemeta;
|
||||
$column = 'meta_key';
|
||||
$key_column = 'meta_id';
|
||||
$value_column = 'meta_value';
|
||||
}
|
||||
|
||||
$key = $this->identifier . '_batch_%';
|
||||
|
||||
$sql = "
|
||||
SELECT *
|
||||
FROM {$table}
|
||||
WHERE {$column} LIKE %s
|
||||
ORDER BY {$key_column} ASC
|
||||
";
|
||||
|
||||
if ( ! empty( $limit ) ) {
|
||||
$sql .= " LIMIT {$limit}";
|
||||
}
|
||||
|
||||
$items = $wpdb->get_results( $wpdb->prepare( $sql, $key ) );
|
||||
|
||||
$batches = array();
|
||||
|
||||
if ( ! empty( $items ) ) {
|
||||
$batches = array_map(
|
||||
function ( $item ) use ( $column, $value_column ) {
|
||||
$batch = new stdClass();
|
||||
$batch->key = $item->$column;
|
||||
$batch->data = AS3CF_Utils::maybe_unserialize( $item->$value_column );
|
||||
|
||||
return $batch;
|
||||
},
|
||||
$items
|
||||
);
|
||||
}
|
||||
|
||||
return $batches;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle
|
||||
*
|
||||
* Pass each queue item to the task handler, while remaining
|
||||
* within server memory and time limit constraints.
|
||||
*/
|
||||
protected function handle() {
|
||||
$this->lock_process();
|
||||
|
||||
/**
|
||||
* Number of seconds to sleep between batches. Defaults to 0 seconds, minimum 0.
|
||||
*/
|
||||
$throttle_seconds = apply_filters( 'as3cf_seconds_between_batches', 0 );
|
||||
|
||||
do {
|
||||
$batch = $this->get_batch();
|
||||
|
||||
foreach ( $batch->data as $key => $value ) {
|
||||
if ( $this->time_exceeded() || $this->memory_exceeded() ) {
|
||||
// Batch limits reached
|
||||
break;
|
||||
}
|
||||
|
||||
// Keep the batch up to date while processing it.
|
||||
$this->update( $batch->key, $batch->data );
|
||||
|
||||
$task = $this->task( $value );
|
||||
|
||||
if ( false !== $task ) {
|
||||
$batch->data[ $key ] = $task;
|
||||
} else {
|
||||
unset( $batch->data[ $key ] );
|
||||
}
|
||||
|
||||
// Let the server breathe a little.
|
||||
sleep( $throttle_seconds );
|
||||
}
|
||||
|
||||
// Update or delete current batch
|
||||
if ( ! empty( $batch->data ) ) {
|
||||
$this->update( $batch->key, $batch->data );
|
||||
} else {
|
||||
$this->delete( $batch->key );
|
||||
}
|
||||
} while ( ! $this->time_exceeded() && ! $this->memory_exceeded() && ! $this->is_queue_empty() );
|
||||
|
||||
$this->unlock_process();
|
||||
|
||||
// Start next batch or complete process
|
||||
if ( ! $this->is_queue_empty() ) {
|
||||
$this->dispatch();
|
||||
} else {
|
||||
$this->complete();
|
||||
}
|
||||
|
||||
wp_die();
|
||||
}
|
||||
|
||||
/**
|
||||
* Memory exceeded
|
||||
*
|
||||
* Ensures the batch process never exceeds 90%
|
||||
* of the maximum WordPress memory.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function memory_exceeded() {
|
||||
return $this->as3cf->memory_exceeded( $this->identifier . '_memory_exceeded' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Time exceeded
|
||||
*
|
||||
* Ensures the batch never exceeds a sensible time limit.
|
||||
* A timeout limit of 30s is common on shared hosting.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function time_exceeded() {
|
||||
$finish = $this->start_time + apply_filters( 'as3cf_default_time_limit', 20 ); // 20 seconds
|
||||
$return = false;
|
||||
|
||||
if ( time() >= $finish ) {
|
||||
$return = true;
|
||||
}
|
||||
|
||||
return apply_filters( $this->identifier . '_time_exceeded', $return );
|
||||
}
|
||||
|
||||
/**
|
||||
* Complete
|
||||
*
|
||||
* Override if applicable, but ensure that the below actions are
|
||||
* performed, or, call parent::complete().
|
||||
*/
|
||||
protected function complete() {
|
||||
delete_site_option( $this->get_status_key() );
|
||||
|
||||
$this->clear_cron_healthcheck();
|
||||
|
||||
$this->completed();
|
||||
do_action( $this->identifier . '_completed' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when background process has completed.
|
||||
*/
|
||||
abstract protected function completed();
|
||||
|
||||
/**
|
||||
* Schedule cron health check.
|
||||
*/
|
||||
protected function schedule_cron_healthcheck() {
|
||||
$this->as3cf->schedule_event( $this->identifier . '_cron', 'as3cf_tool_cron_interval' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear cron health check.
|
||||
*/
|
||||
protected function clear_cron_healthcheck() {
|
||||
$this->as3cf->clear_scheduled_event( $this->identifier . '_cron' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle cron health check
|
||||
*
|
||||
* Restart the background process if not already running
|
||||
* and data exists in the queue.
|
||||
*/
|
||||
public function handle_cron_healthcheck() {
|
||||
if ( $this->is_process_running() ) {
|
||||
// Background process already running
|
||||
exit;
|
||||
}
|
||||
|
||||
if ( $this->is_queue_empty() ) {
|
||||
// No data to process
|
||||
$this->as3cf->clear_scheduled_event( $this->identifier . '_cron' );
|
||||
exit;
|
||||
}
|
||||
|
||||
$this->dispatch();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
abstract protected function task( $item );
|
||||
|
||||
}
|
||||
155
classes/pro/as3cf-pro-installer.php
Normal file
155
classes/pro/as3cf-pro-installer.php
Normal file
@@ -0,0 +1,155 @@
|
||||
<?php
|
||||
|
||||
class AS3CF_Pro_Installer extends AS3CF_Compatibility_Check {
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $required_plugins;
|
||||
|
||||
/**
|
||||
* @var AS3CF_Pro_Plugin_Installer
|
||||
*/
|
||||
protected $plugin_installer;
|
||||
|
||||
/**
|
||||
* AS3CF_Pro_Installer constructor.
|
||||
*
|
||||
* @param string $plugin_file_path
|
||||
*/
|
||||
public function __construct( $plugin_file_path ) {
|
||||
parent::__construct(
|
||||
'WP Offload Media',
|
||||
'amazon-s3-and-cloudfront-pro',
|
||||
$plugin_file_path
|
||||
);
|
||||
|
||||
// Fire up the plugin installer
|
||||
if ( is_admin() ) {
|
||||
$this->plugin_installer = new AS3CF_Pro_Plugin_Installer( 'installer', $this->plugin_slug, $this->plugin_file_path );
|
||||
$this->plugin_installer->set_plugins_to_install( $this->required_plugins_not_installed() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Are all the required plugins installed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_setup() {
|
||||
$plugins_not_installed = $this->required_plugins_not_installed();
|
||||
|
||||
return empty( $plugins_not_installed );
|
||||
}
|
||||
|
||||
/**
|
||||
* Are all the required plugins activated?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function are_required_plugins_activated() {
|
||||
$plugins_not_activated = $this->required_plugins_not_activated();
|
||||
|
||||
return empty( $plugins_not_activated );
|
||||
}
|
||||
|
||||
/**
|
||||
* If the plugin is setup use the default compatible check
|
||||
*
|
||||
* @param bool $installed Check to see if the plugins are installed
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function is_compatible( $installed = true ) {
|
||||
if ( $this->is_setup() || false === $installed ) {
|
||||
return parent::is_compatible();
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* The required plugins for this plugin
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_required_plugins() {
|
||||
if ( is_null( $this->required_plugins ) ) {
|
||||
$this->required_plugins = array();
|
||||
}
|
||||
|
||||
return $this->required_plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any of the required plugins are installed
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function required_plugins_not_installed() {
|
||||
$plugins = array();
|
||||
$required_plugins = $this->get_required_plugins();
|
||||
|
||||
foreach ( $required_plugins as $slug => $plugin ) {
|
||||
$filename = ( isset( $plugin['file'] ) ) ? $plugin['file'] : $slug;
|
||||
if ( ! class_exists( $plugin['class'] ) && ! file_exists( WP_PLUGIN_DIR . '/' . $slug . '/' . $filename . '.php' ) ) {
|
||||
$plugins[ $slug ] = $plugin['name'];
|
||||
}
|
||||
}
|
||||
|
||||
return $plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if any of the required plugins are activated
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function required_plugins_not_activated() {
|
||||
$plugins = array();
|
||||
$required_plugins = $this->get_required_plugins();
|
||||
|
||||
foreach ( $required_plugins as $slug => $plugin ) {
|
||||
if ( ! class_exists( $plugin['class'] ) ) {
|
||||
$plugins[ $slug ] = $plugin['name'];
|
||||
}
|
||||
}
|
||||
|
||||
return $plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the plugin info URL for thickbox
|
||||
*
|
||||
* @param string $slug
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
function get_plugin_info_url( $slug ) {
|
||||
return self_admin_url( 'plugin-install.php?tab=plugin-information&plugin=' . $slug . '&TB_iframe=true&width=600&height=800' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a custom install notice if the plugin is not setup
|
||||
*/
|
||||
function get_admin_notice() {
|
||||
$plugins_not_installed = $this->required_plugins_not_installed();
|
||||
if ( empty( $plugins_not_installed ) ) {
|
||||
parent::get_admin_notice();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->plugin_installer->load_installer_assets();
|
||||
|
||||
if ( $notices = get_site_transient( 'as3cfpro_installer_notices' ) ) {
|
||||
if ( isset( $notices['filesystem_error'] ) ) {
|
||||
// Don't show the installer notice if we have filesystem credential issues
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$install_notice = untrailingslashit( plugin_dir_path( $this->plugin_file_path ) ) . '/view/pro/install-notice.php';
|
||||
include $install_notice;
|
||||
}
|
||||
}
|
||||
1080
classes/pro/as3cf-pro-licences-updates.php
Normal file
1080
classes/pro/as3cf-pro-licences-updates.php
Normal file
File diff suppressed because it is too large
Load Diff
122
classes/pro/as3cf-pro-plugin-compatibility.php
Normal file
122
classes/pro/as3cf-pro-plugin-compatibility.php
Normal file
@@ -0,0 +1,122 @@
|
||||
<?php
|
||||
/**
|
||||
* Pro Plugin Compatibility
|
||||
*
|
||||
* @package amazon-s3-and-cloudfront-pro
|
||||
* @subpackage Classes/Plugin-Compatibility
|
||||
* @copyright Copyright (c) 2015, Delicious Brains
|
||||
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
|
||||
* @since 0.8.3
|
||||
*/
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* AS3CF_Pro_Plugin_Compatibility Class
|
||||
*
|
||||
* This class handles compatibility code for third party plugins used in conjunction with AS3CF Pro
|
||||
*
|
||||
* @since 0.8.3
|
||||
*/
|
||||
class AS3CF_Pro_Plugin_Compatibility extends AS3CF_Plugin_Compatibility {
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $plugin_functions_abort_upload;
|
||||
|
||||
/**
|
||||
* @var AS3CF_Pro_Plugin_Installer
|
||||
*/
|
||||
protected $plugin_installer;
|
||||
|
||||
/**
|
||||
* @param Amazon_S3_And_CloudFront_Pro $as3cf
|
||||
*/
|
||||
function __construct( $as3cf ) {
|
||||
parent::__construct( $as3cf );
|
||||
|
||||
if ( is_admin() ) {
|
||||
$this->plugin_installer = new AS3CF_Pro_Plugin_Installer( 'addons', $this->as3cf->get_plugin_slug( true ), $this->as3cf->get_plugin_file_path() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the compatibility hooks
|
||||
*/
|
||||
function compatibility_init() {
|
||||
$this->set_plugin_functions_abort_upload();
|
||||
|
||||
add_filter( 'as3cf_pre_update_attachment_metadata', array( $this, 'abort_update_attachment_metadata' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the abort upload functions property.
|
||||
*/
|
||||
function set_plugin_functions_abort_upload() {
|
||||
$functions = array(
|
||||
'gambit_otf_regen_thumbs_media_downsize', // https://wordpress.org/plugins/otf-regenerate-thumbnails/
|
||||
'ewww_image_optimizer_resize_from_meta_data', // https://wordpress.org/plugins/ewww-image-optimizer/
|
||||
);
|
||||
|
||||
/**
|
||||
* Filter the array of functions which should lead to aborting our
|
||||
* `wp_attachment_metadata_update` filter.
|
||||
*
|
||||
* @param array $functions Plugins functions which should lead to aborting
|
||||
* our `wp_attachment_metadata_update` filter.
|
||||
*/
|
||||
$functions = apply_filters( 'wpos3_plugin_functions_to_abort_upload', $functions ); // Backwards compatibility
|
||||
|
||||
/**
|
||||
* Filter the array of functions which should lead to aborting our
|
||||
* `wp_attachment_metadata_update` filter.
|
||||
*
|
||||
* @param array $functions Plugins functions which should lead to aborting
|
||||
* our `wp_attachment_metadata_update` filter.
|
||||
*/
|
||||
$functions = apply_filters( 'as3cf_plugin_functions_to_abort_upload', $functions );
|
||||
|
||||
// Unset any function that doesn't exist.
|
||||
foreach ( (array) $functions as $key => $function ) {
|
||||
if ( ! function_exists( $function ) ) {
|
||||
unset( $functions[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
$this->plugin_functions_abort_upload = $functions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Abort our upload to S3 on wp_attachment_metadata_update from different plugins
|
||||
* as as we have used the stream wrapper to do any uploading to S3.
|
||||
*
|
||||
* @param bool $pre
|
||||
* @param array $data
|
||||
* @param int $post_id
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function abort_update_attachment_metadata( $pre, $data, $post_id ) {
|
||||
if ( empty( $this->plugin_functions_abort_upload ) ) {
|
||||
return $pre;
|
||||
}
|
||||
|
||||
$callers = debug_backtrace(); // phpcs:ignore
|
||||
foreach ( $callers as $caller ) {
|
||||
if ( isset( $caller['function'] ) && in_array( $caller['function'], $this->plugin_functions_abort_upload ) ) {
|
||||
if ( $this->as3cf->get_setting( 'remove-local-file' ) || ! file_exists( get_attached_file( $post_id, true ) ) ) {
|
||||
// abort the rest of the update_attachment_metadata hook
|
||||
// if the file doesn't exist on the server, as the stream wrapper
|
||||
// has taken care of the rest.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $pre;
|
||||
}
|
||||
}
|
||||
364
classes/pro/as3cf-pro-plugin-installer.php
Normal file
364
classes/pro/as3cf-pro-plugin-installer.php
Normal file
@@ -0,0 +1,364 @@
|
||||
<?php
|
||||
|
||||
class AS3CF_Pro_Plugin_Installer {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $process_key;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_slug;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $plugin_file_path;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $plugins_to_install;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $installer_notices;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $installer_action = 'as3cfpro-install-plugins';
|
||||
|
||||
/**
|
||||
* AS3CF_Pro_Plugin_Installer constructor.
|
||||
*
|
||||
* @param string $process_key
|
||||
* @param string $plugin_slug
|
||||
* @param string $plugin_file_path
|
||||
*/
|
||||
public function __construct( $process_key, $plugin_slug, $plugin_file_path ) {
|
||||
$this->process_key = $process_key;
|
||||
$this->plugin_slug = $plugin_slug;
|
||||
$this->plugin_file_path = $plugin_file_path;
|
||||
|
||||
add_action( 'wp_ajax_as3cfpro_install_plugins_' . $this->process_key, array( $this, 'ajax_install_plugins' ) );
|
||||
add_action( 'admin_init', array( $this, 'maybe_install_plugins' ) );
|
||||
add_action( 'admin_init', array( $this, 'installer_redirect' ) );
|
||||
add_action( 'admin_notices', array( $this, 'maybe_display_installer_notices' ) );
|
||||
add_action( 'network_admin_notices', array( $this, 'maybe_display_installer_notices' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the plugins to be installed
|
||||
*
|
||||
* @param array $plugins_to_install
|
||||
*/
|
||||
public function set_plugins_to_install( $plugins_to_install ) {
|
||||
set_site_transient( 'as3cfpro_plugins_to_install_' . $this->process_key, $plugins_to_install );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the plugins to be installed
|
||||
*/
|
||||
public function get_plugins_to_install() {
|
||||
return get_site_transient( 'as3cfpro_plugins_to_install_' . $this->process_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the scripts and styles required for the plugin installer
|
||||
*/
|
||||
function load_installer_assets() {
|
||||
$plugin_version = $GLOBALS['aws_meta'][ $this->plugin_slug ]['version'];
|
||||
$version = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? time() : $plugin_version;
|
||||
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
|
||||
|
||||
$src = plugins_url( 'assets/js/pro/installer' . $suffix . '.js', $this->plugin_file_path );
|
||||
wp_enqueue_script( 'as3cf-pro-installer', $src, array( 'jquery', 'wp-util' ), $version, true );
|
||||
|
||||
$script_args = array(
|
||||
'strings' => array(
|
||||
'installing' => __( 'Installing', 'amazon-s3-and-cloudfront' ),
|
||||
'error_installing' => __( 'There was an error during the installation', 'amazon-s3-and-cloudfront' ),
|
||||
),
|
||||
'nonces' => array(
|
||||
'install_plugins' => wp_create_nonce( 'install-plugins' ),
|
||||
),
|
||||
);
|
||||
|
||||
wp_localize_script( 'as3cf-pro-installer', 'as3cfpro_installer', $script_args );
|
||||
|
||||
// Load thickbox scripts and style so the links work on all pages in dashboard
|
||||
add_thickbox();
|
||||
wp_enqueue_script( 'plugin-install' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Install and activate all required plugins
|
||||
*
|
||||
* @return bool|string|WP_Error
|
||||
*/
|
||||
function install_plugins() {
|
||||
global $as3cfpro_compat_check;
|
||||
if ( ! $as3cfpro_compat_check->check_capabilities() ) {
|
||||
return new WP_Error( 'exception', __( 'You do not have sufficient permissions to install plugins on this site.' ) );
|
||||
}
|
||||
|
||||
$this->installer_notices = array();
|
||||
$plugins_to_install = $this->get_plugins_to_install();
|
||||
|
||||
$plugins_activated = 0;
|
||||
foreach ( $plugins_to_install as $slug => $plugin ) {
|
||||
if ( $this->install_plugin( $slug ) ) {
|
||||
$plugins_activated++;
|
||||
}
|
||||
}
|
||||
|
||||
set_site_transient( 'as3cfpro_installer_notices', $this->installer_notices, 30 );
|
||||
delete_site_transient( 'as3cfpro_plugins_to_install_' . $this->process_key );
|
||||
|
||||
if ( $plugins_activated === count( $this->plugins_to_install ) ) {
|
||||
// All plugins installed and activated successfully
|
||||
$url = add_query_arg( array( 'as3cfpro-install' => 1 ), network_admin_url( 'plugins.php' ) );
|
||||
|
||||
return esc_url_raw( $url );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retry to install plugins if there has been a filesystem credential issue
|
||||
*/
|
||||
function maybe_install_plugins() {
|
||||
if ( AS3CF_Utils::is_ajax() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $pagenow;
|
||||
if ( 'plugins.php' !== $pagenow ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $_GET['action'] ) || $this->installer_action != $_GET['action'] ) {
|
||||
return;
|
||||
}
|
||||
|
||||
check_admin_referer( $this->installer_action );
|
||||
|
||||
$this->request_filesystem_credentials();
|
||||
|
||||
$result = $this->install_plugins();
|
||||
|
||||
$redirect = network_admin_url( 'plugins.php' );
|
||||
if ( ! is_wp_error( $result ) && $result !== true ) {
|
||||
$redirect = $result;
|
||||
}
|
||||
|
||||
wp_redirect( $redirect );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* AJAX handler for installing the required plugins.
|
||||
*/
|
||||
public function ajax_install_plugins() {
|
||||
check_ajax_referer( 'install-plugins', 'nonce' );
|
||||
|
||||
$response = array(
|
||||
'redirect' => network_admin_url( 'plugins.php' ), // redirect to the plugins page by default
|
||||
);
|
||||
|
||||
$result = $this->install_plugins();
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$response['error'] = $result->get_error_message();
|
||||
wp_send_json_error( $response );
|
||||
}
|
||||
|
||||
if ( $result !== true ) {
|
||||
$response['redirect'] = $result;
|
||||
}
|
||||
|
||||
wp_send_json_success( $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Redirect to the AWS or Offload Media page after successfully installing the plugins
|
||||
*/
|
||||
function installer_redirect() {
|
||||
if ( AS3CF_Utils::is_ajax() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $pagenow;
|
||||
if ( 'plugins.php' !== $pagenow ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $_GET['as3cfpro-install'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $as3cf_compat_check;
|
||||
if ( ! $as3cf_compat_check->is_compatible( false ) ) {
|
||||
// Do not redirect if the pro plugin is not compatible
|
||||
return;
|
||||
}
|
||||
|
||||
delete_site_transient( 'as3cfpro_installer_notices' );
|
||||
|
||||
$url = add_query_arg(
|
||||
array( 'page' => 'amazon-s3-and-cloudfront' ),
|
||||
network_admin_url( is_multisite() ? 'settings.php' : 'options-general.php' )
|
||||
);
|
||||
|
||||
wp_redirect( esc_url_raw( $url ) );
|
||||
exit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Install and activate a plugin
|
||||
*
|
||||
* @param string $slug
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function install_plugin( $slug ) {
|
||||
$status = array( 'slug' => $slug );
|
||||
|
||||
include_once ABSPATH . 'wp-admin/includes/class-wp-upgrader.php';
|
||||
include_once ABSPATH . 'wp-admin/includes/plugin-install.php';
|
||||
|
||||
$api = plugins_api( 'plugin_information', array(
|
||||
'slug' => $slug,
|
||||
'fields' => array( 'sections' => false ),
|
||||
) );
|
||||
|
||||
if ( is_wp_error( $api ) ) {
|
||||
$status['error'] = $api->get_error_message();
|
||||
$this->end_install( $status );
|
||||
}
|
||||
|
||||
$upgrader = new Plugin_Upgrader( new Automatic_Upgrader_Skin() );
|
||||
$result = $upgrader->install( $api->download_link );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
$status['error'] = $result->get_error_message();
|
||||
$this->end_install( $status );
|
||||
|
||||
return false;
|
||||
} elseif ( is_null( $result ) ) {
|
||||
$status['error'] = __( 'Unable to connect to the filesystem. Please confirm your credentials.' );
|
||||
|
||||
$this->installer_notices['filesystem_error'] = true;
|
||||
$this->end_install( $status );
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$installed_plugin = get_plugins( '/' . $slug );
|
||||
|
||||
if ( ! empty( $installed_plugin ) ) {
|
||||
$key = array_keys( $installed_plugin );
|
||||
$key = reset( $key );
|
||||
$file = $slug . '/' . $key;
|
||||
|
||||
$network_wide = is_multisite();
|
||||
$activated = activate_plugin( $file, '', $network_wide );
|
||||
} else {
|
||||
$activated = false;
|
||||
}
|
||||
|
||||
$plugin_activated = false;
|
||||
if ( false === $activated || is_wp_error( $activated ) ) {
|
||||
$warning = ' ' . __( 'but not activated', 'amazon-s3-and-cloudfront' );
|
||||
if ( is_wp_error( $activated ) ) {
|
||||
$warning .= ': ' . $activated->get_error_message();
|
||||
}
|
||||
|
||||
$status['warning'] = $warning;
|
||||
} else {
|
||||
$plugin_activated = true;
|
||||
}
|
||||
|
||||
$this->end_install( $status );
|
||||
|
||||
return $plugin_activated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add the outcome of the install to the installer notices array to be set in a transient
|
||||
*
|
||||
* @param array $status
|
||||
*/
|
||||
protected function end_install( $status ) {
|
||||
$plugins = $this->get_plugins_to_install();
|
||||
|
||||
$class = 'updated';
|
||||
$message = sprintf( __( '%s installed successfully', 'amazon-s3-and-cloudfront' ), $plugins[ $status['slug'] ] );
|
||||
if ( isset( $status['error'] ) ) {
|
||||
$class = 'error';
|
||||
$message = sprintf( __( '%s not installed', 'amazon-s3-and-cloudfront' ), $plugins[ $status['slug'] ] );
|
||||
$message .= ': ' . $status['error'];
|
||||
}
|
||||
|
||||
if ( isset( $status['warning'] ) ) {
|
||||
$message .= $status['warning'];
|
||||
}
|
||||
|
||||
$this->installer_notices['notices'][] = array( 'message' => $message, 'class' => $class );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the request filesystem credentials form
|
||||
*
|
||||
* @return string Form HTML
|
||||
*/
|
||||
protected function request_filesystem_credentials() {
|
||||
$url = wp_nonce_url( 'plugins.php?action=' . $this->installer_action, $this->installer_action );
|
||||
ob_start();
|
||||
request_filesystem_credentials( $url );
|
||||
$data = ob_get_contents();
|
||||
ob_end_clean();
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Display plugin installer notices
|
||||
*/
|
||||
public function maybe_display_installer_notices() {
|
||||
if ( false === ( $notices = get_site_transient( 'as3cfpro_installer_notices' ) ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! isset( $notices['notices'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $as3cf_compat_check;
|
||||
if ( ! $as3cf_compat_check->check_capabilities() ) {
|
||||
// User can't install plugins anyway, bail.
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $notices['notices'] as $notice ) {
|
||||
print '<div class="as3cf-pro-installer-notice ' . $notice['class'] . '"><p>' . $notice['message'] . '</p></div>';
|
||||
}
|
||||
|
||||
delete_site_transient( 'as3cfpro_installer_notices' );
|
||||
|
||||
if ( isset( $notices['filesystem_error'] ) ) {
|
||||
$data = $this->request_filesystem_credentials();
|
||||
if ( ! empty( $data ) ) {
|
||||
echo '<div class="as3cfpro-installer-filesystem-creds">';
|
||||
echo $data;
|
||||
echo '</div>';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
96
classes/pro/as3cf-pro-utils.php
Normal file
96
classes/pro/as3cf-pro-utils.php
Normal file
@@ -0,0 +1,96 @@
|
||||
<?php
|
||||
/**
|
||||
* Pro Plugin Utilities
|
||||
*
|
||||
* @package amazon-s3-and-cloudfront-pro
|
||||
* @subpackage Classes/Utils
|
||||
* @copyright Copyright (c) 2015, Delicious Brains
|
||||
* @license http://opensource.org/licenses/gpl-2.0.php GNU Public License
|
||||
* @since 1.0
|
||||
*/
|
||||
|
||||
// Exit if accessed directly
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* AS3CF_Pro_Utils Class
|
||||
*
|
||||
* This class contains utility functions that need to be available
|
||||
* across the Pro plugin codebase
|
||||
*
|
||||
* @since 1.0
|
||||
*/
|
||||
class AS3CF_Pro_Utils {
|
||||
|
||||
/**
|
||||
* Delete wildcard options
|
||||
*
|
||||
* @param array|string $keys
|
||||
*/
|
||||
public static function delete_wildcard_options( $keys ) {
|
||||
global $wpdb;
|
||||
|
||||
$table = $wpdb->options;
|
||||
$column = 'option_name';
|
||||
|
||||
if ( is_multisite() ) {
|
||||
$table = $wpdb->sitemeta;
|
||||
$column = 'meta_key';
|
||||
}
|
||||
|
||||
// Convert string values to array
|
||||
if ( ! is_array( $keys ) ) {
|
||||
$keys = array( $keys );
|
||||
}
|
||||
|
||||
foreach ( $keys as $key ) {
|
||||
$wpdb->query( $wpdb->prepare( "
|
||||
DELETE FROM {$table}
|
||||
WHERE {$column} LIKE %s
|
||||
", $key ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the keys of background jobs that could be running
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_batch_job_keys() {
|
||||
$keys = array(
|
||||
'as3cf_find_replace_batch_%',
|
||||
'as3cf_legacy_upload_%',
|
||||
'as3cf_media_actions_batch_%',
|
||||
'as3cf_settings_change_batch_%',
|
||||
'as3cf_minify_batch_%',
|
||||
'as3cf_process_assets_batch_%',
|
||||
);
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively collapses an empty array structure.
|
||||
*
|
||||
* @param mixed $array
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public static function array_prune_recursive( $array ) {
|
||||
if ( ! is_array( $array ) ) {
|
||||
return $array;
|
||||
}
|
||||
|
||||
foreach ( $array as $key => &$value ) {
|
||||
$value = self::array_prune_recursive( $value );
|
||||
|
||||
if ( empty( $value ) && ! is_numeric( $value ) ) {
|
||||
unset( $array[ $key ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $array;
|
||||
}
|
||||
}
|
||||
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' );
|
||||
}
|
||||
}
|
||||
576
classes/pro/background-tool.php
Normal file
576
classes/pro/background-tool.php
Normal file
@@ -0,0 +1,576 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro;
|
||||
|
||||
use Amazon_S3_And_CloudFront;
|
||||
use AS3CF_Utils;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Background_Tool_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Upgrades\Upgrade;
|
||||
|
||||
abstract class Background_Tool extends Tool {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'background-tool';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $view = 'background-tool';
|
||||
|
||||
/**
|
||||
* @var Background_Tool_Process
|
||||
*/
|
||||
protected $background_process;
|
||||
|
||||
/**
|
||||
* @var null|array
|
||||
*/
|
||||
private $batch = null;
|
||||
|
||||
/**
|
||||
* Limit the item types that this tool handles. Leave empty to handle all registered item types
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $source_types = array();
|
||||
|
||||
/**
|
||||
* Initialize the tool.
|
||||
*/
|
||||
public function init() {
|
||||
parent::init();
|
||||
|
||||
$this->background_process = $this->get_background_process_class();
|
||||
|
||||
// During an upgrade, cancel all background processes.
|
||||
if ( Upgrade::is_locked() && ( $this->is_processing() || $this->is_queued() ) ) {
|
||||
$this->handle_cancel();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get info for tool, including current status.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_info() {
|
||||
$info = $this->get_default_info();
|
||||
|
||||
$this->maybe_add_loopback_request_notice( $info );
|
||||
|
||||
return array_merge( parent::get_info(), $info );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of key names for tools that are related to the current tool.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_related_tools() {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get default info for tool, including current status.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_default_info(): array {
|
||||
return array(
|
||||
'name' => $this->get_name(),
|
||||
'title' => $this->get_title_text(),
|
||||
'title_partial_complete' => $this->get_title_text_partial_complete(),
|
||||
'title_complete' => $this->get_title_text_complete(),
|
||||
'more_info' => $this->get_more_info_text(),
|
||||
'related_tools' => $this->get_related_tools(),
|
||||
'prompt' => $this->get_prompt_text(),
|
||||
'doc_url' => $this->get_doc_url(),
|
||||
'doc_desc' => $this->get_doc_desc(),
|
||||
'status_description' => $this->get_status_description(),
|
||||
'short_status_description' => $this->get_short_status_description(),
|
||||
'busy_description' => $this->get_busy_description(),
|
||||
'locked_notification' => $this->get_locked_notification(),
|
||||
'button' => $this->get_button_text(),
|
||||
'button_partial_complete' => $this->get_button_text_partial_complete(),
|
||||
'button_complete' => $this->get_button_text_complete(),
|
||||
'is_queued' => $this->is_queued(),
|
||||
'is_paused' => $this->is_paused(),
|
||||
'is_cancelled' => $this->is_cancelled(),
|
||||
'is_upgrading' => Upgrade::is_locked(),
|
||||
'progress' => $this->get_progress(),
|
||||
'queue' => $this->get_queue_counts(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* If it looks like this tool is stuck, check loopback site health report and potentially add notice.
|
||||
*
|
||||
* @param array $status
|
||||
*/
|
||||
private function maybe_add_loopback_request_notice( $status ) {
|
||||
$site_health_path = trailingslashit( ABSPATH ) . 'wp-admin/includes/class-wp-site-health.php';
|
||||
|
||||
if (
|
||||
! empty( $status['is_queued'] ) &&
|
||||
empty( $status['is_processing'] ) &&
|
||||
empty( $status['is_paused'] ) &&
|
||||
empty( $status['is_cancelled'] ) &&
|
||||
file_exists( $site_health_path ) &&
|
||||
(
|
||||
false === get_site_transient( $this->prefix . '_loopback_test' ) ||
|
||||
get_site_transient( $this->prefix . '_loopback_test' ) === $this->tool_key
|
||||
)
|
||||
) {
|
||||
set_site_transient( $this->prefix . '_loopback_test', $this->tool_key, 30 );
|
||||
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
require_once $site_health_path;
|
||||
$site_health = new \WP_Site_Health();
|
||||
|
||||
$loopback = $site_health->get_test_loopback_requests();
|
||||
|
||||
if (
|
||||
! empty( $loopback['status'] ) &&
|
||||
'good' !== $loopback['status'] &&
|
||||
! empty( $loopback['label'] ) &&
|
||||
! empty( $loopback['description'] ) ) {
|
||||
$args = array(
|
||||
'type' => 'error',
|
||||
'class' => 'tool-error',
|
||||
'dismissible' => false,
|
||||
'flash' => false,
|
||||
'only_show_to_user' => false,
|
||||
'only_show_on_tab' => $this->tab,
|
||||
'custom_id' => $this->errors_key_prefix . 'loopback_test',
|
||||
'user_capabilities' => array( 'as3cfpro', 'is_plugin_setup' ),
|
||||
);
|
||||
|
||||
$site_health_link = get_dashboard_url( get_current_user_id(), 'site-health.php' );
|
||||
|
||||
$doc_url = $this->as3cf->dbrains_url( '/wp-offload-media/doc/background-processes-not-completing/', array(
|
||||
'utm_campaign' => 'support+docs',
|
||||
) );
|
||||
$doc_link = AS3CF_Utils::dbrains_link( $doc_url, __( 'Background Processes doc', 'amazon-s3-and-cloudfront' ) );
|
||||
|
||||
$message = sprintf( __( 'The background process is stuck. Please ensure that the <strong>loopback request</strong> test is passing in <a href="%1$s">Site Health</a>.<br><br>For troubleshooting tips please see our %2$s.', 'amazon-s3-and-cloudfront' ), $site_health_link, $doc_link );
|
||||
|
||||
$this->as3cf->notices->add_notice( $this->get_error_notice_message( $message ), $args );
|
||||
} else {
|
||||
$this->as3cf->notices->remove_notice_by_id( $this->errors_key_prefix . 'loopback_test' );
|
||||
}
|
||||
} elseif (
|
||||
false === get_site_transient( $this->prefix . '_loopback_test' ) ||
|
||||
get_site_transient( $this->prefix . '_loopback_test' ) === $this->tool_key
|
||||
) {
|
||||
// No other tool is stuck, clear out admin notice if set.
|
||||
$this->as3cf->notices->remove_notice_by_id( $this->errors_key_prefix . 'loopback_test' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more info text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_more_info_text() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prompt text for when tool could be run in response to settings change.
|
||||
* Defaults to more info text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_prompt_text() {
|
||||
return static::get_more_info_text();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns doc URL for use in UI help buttons etc.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* Note: Should be overridden if a dedicated help doc is available for the tool.
|
||||
*/
|
||||
public function get_doc_url() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns doc description for use in UI help buttons etc.
|
||||
*
|
||||
* @return string
|
||||
*
|
||||
* Note: Should be overridden if a dedicated doc description is required for the tool.
|
||||
* However, the UI will use a reasonable default in none supplied here.
|
||||
*/
|
||||
public function get_doc_desc() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get status description.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_status_description(): string {
|
||||
if ( $this->is_processing() && ( $this->is_cancelled() || $this->is_paused() ) ) {
|
||||
return __( 'Completing current batch.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
if ( $this->is_paused() ) {
|
||||
return __( 'Paused', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
if ( $this->is_queued() ) {
|
||||
return $this->get_queued_status();
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short status description.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_short_status_description(): string {
|
||||
if ( $this->is_processing() && $this->is_cancelled() ) {
|
||||
return _x( 'Stopping…', 'Short tool stopping message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
if ( $this->is_processing() && $this->is_paused() ) {
|
||||
return _x( 'Pausing…', 'Short tool pausing message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
if ( $this->is_paused() ) {
|
||||
return _x( 'Paused', 'Short tool paused message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
if ( $this->is_queued() ) {
|
||||
return $this->get_short_queued_status();
|
||||
}
|
||||
|
||||
return $this->get_status_description();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get busy description.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_busy_description() {
|
||||
return _x( 'Processing…', 'Short tool running message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get description for locked notification.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_locked_notification() {
|
||||
return sprintf(
|
||||
__(
|
||||
'<strong>Settings Locked</strong> — You can\'t change any of your settings until the "<a href="#/tools">%s</a>" tool has completed.',
|
||||
'amazon-s3-and-cloudfront'
|
||||
),
|
||||
$this->get_name()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle start.
|
||||
*
|
||||
* Note: Dynamically called by `DeliciousBrains\WP_Offload_Media\Pro\Tools_Manager::perform_action`.
|
||||
*/
|
||||
public function handle_start() {
|
||||
if ( $this->is_queued() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->clear_errors();
|
||||
$this->as3cf->notices->dismiss_notice( $this->errors_key );
|
||||
|
||||
$session = $this->create_session();
|
||||
|
||||
$this->background_process->push_to_queue( $session )->save()->dispatch();
|
||||
do_action( $this->prefix . '_' . $this->tool_key . '_started' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle cancel.
|
||||
*
|
||||
* Note: Dynamically called by `DeliciousBrains\WP_Offload_Media\Pro\Tools_Manager::perform_action`.
|
||||
*/
|
||||
public function handle_cancel() {
|
||||
if ( ! $this->is_queued() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->background_process->cancel();
|
||||
|
||||
// Force this process to think there is no current batch
|
||||
// as this process will not be processing one anyway.
|
||||
// This ensures subsequent in-process checks don't return a mixed view.
|
||||
$this->batch = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle pause resume.
|
||||
*
|
||||
* Note: Dynamically called by `DeliciousBrains\WP_Offload_Media\Pro\Tools_Manager::perform_action`.
|
||||
*/
|
||||
public function handle_pause_resume() {
|
||||
if ( ! $this->is_queued() || $this->is_cancelled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( $this->is_paused() ) {
|
||||
$this->background_process->resume();
|
||||
} else {
|
||||
$this->background_process->pause();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create session.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function create_session() {
|
||||
/** @var Amazon_S3_And_CloudFront $as3cf */
|
||||
global $as3cf;
|
||||
|
||||
$source_types = array();
|
||||
$tool_source_types = empty( $this->source_types ) ? array_keys( $as3cf->get_source_type_classes() ) : $this->source_types;
|
||||
foreach ( $tool_source_types as $source_type ) {
|
||||
$source_types[ $source_type ] = 0;
|
||||
}
|
||||
|
||||
$session = array(
|
||||
'total_items' => 0,
|
||||
'processed_items' => 0,
|
||||
'blogs_processed' => false,
|
||||
'blogs' => array(),
|
||||
);
|
||||
|
||||
foreach ( AS3CF_Utils::get_all_blog_table_prefixes() as $blog_id => $prefix ) {
|
||||
$session['blogs'][ $blog_id ] = array(
|
||||
'prefix' => $prefix,
|
||||
'processed' => $source_types,
|
||||
'total_items' => null,
|
||||
'last_source_id' => array(),
|
||||
);
|
||||
}
|
||||
|
||||
return $session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get progress.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_progress() {
|
||||
$batch = $this->get_batch();
|
||||
|
||||
if ( empty( $batch ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$data = $batch->data;
|
||||
$data = array_shift( $data );
|
||||
|
||||
if ( empty( $data['total_items'] ) || ! isset( $data['processed_items'] ) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return absint( $data['processed_items'] / $data['total_items'] * 100 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Is queued?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_queued(): bool {
|
||||
$batch = $this->get_batch();
|
||||
|
||||
if ( empty( $batch ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get total and processed counts for queue.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_queue_counts() {
|
||||
$counts = array(
|
||||
'total' => 0,
|
||||
'processed' => 0,
|
||||
);
|
||||
|
||||
$batch = $this->get_batch();
|
||||
|
||||
if ( empty( $batch ) ) {
|
||||
return $counts;
|
||||
}
|
||||
|
||||
$data = $batch->data;
|
||||
$data = array_shift( $data );
|
||||
|
||||
if ( ! isset( $data['total_items'] ) || ! isset( $data['processed_items'] ) ) {
|
||||
return $counts;
|
||||
}
|
||||
|
||||
$counts['total'] = $data['total_items'];
|
||||
$counts['processed'] = $data['processed_items'];
|
||||
|
||||
return $counts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the tool paused?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_paused() {
|
||||
return $this->background_process->is_paused();
|
||||
}
|
||||
|
||||
/**
|
||||
* Has the tool been cancelled?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_cancelled() {
|
||||
return $this->background_process->is_cancelled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the background process currently running?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_processing() {
|
||||
return $this->background_process->is_process_running();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get background process batch.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_batch() {
|
||||
if ( is_null( $this->batch ) ) {
|
||||
$batch = $this->background_process->get_batches( 1 );
|
||||
|
||||
if ( empty( $batch ) ) {
|
||||
$this->batch = array();
|
||||
} else {
|
||||
$this->batch = array_shift( $batch );
|
||||
}
|
||||
}
|
||||
|
||||
return $this->batch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the tool currently active, e.g. starting, working, paused or cleaning up?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_active(): bool {
|
||||
return $this->is_queued() || $this->is_processing() || $this->is_paused() || $this->is_cancelled();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tool's name. Defaults to its title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return $this->get_title_text();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_title_text();
|
||||
|
||||
/**
|
||||
* Get title text for when tool is partially complete.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text_partial_complete() {
|
||||
return $this->get_title_text();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title text for when tool is complete.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text_complete() {
|
||||
return $this->get_title_text();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_button_text();
|
||||
|
||||
/**
|
||||
* Get button text for when tool is partially complete.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_button_text_partial_complete() {
|
||||
return $this->get_button_text();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button text for when tool is complete.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_button_text_complete() {
|
||||
return $this->get_button_text();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract public function get_queued_status(): string;
|
||||
|
||||
/**
|
||||
* Get a shortened queued status message.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_short_queued_status(): string {
|
||||
return _x( 'Processing…', 'Short tool running message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get background process class.
|
||||
*
|
||||
* @return Background_Tool_Process|null
|
||||
*/
|
||||
abstract protected function get_background_process_class();
|
||||
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations\Assets\API\V1;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\API\API;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Integrations\Assets\Domain_Check_Response;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Response;
|
||||
|
||||
class Assets_Domain_Check extends API {
|
||||
/** @var int */
|
||||
protected static $version = 1;
|
||||
|
||||
/** @var string */
|
||||
protected static $name = 'assets-domain-check';
|
||||
|
||||
/**
|
||||
* Register REST API routes.
|
||||
*/
|
||||
public function register_routes() {
|
||||
register_rest_route(
|
||||
static::api_namespace(),
|
||||
static::route() . '(?P<key>[\w\d=]+)',
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
'callback' => array( $this, 'get_domain_check' ),
|
||||
'permission_callback' => '__return_true', // public access
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to a GET request to the domain check route, with the given key.
|
||||
*
|
||||
* @param WP_REST_Request $request
|
||||
*
|
||||
* @return WP_REST_Response
|
||||
*/
|
||||
public function get_domain_check( WP_REST_Request $request ): WP_REST_Response {
|
||||
$response = new Domain_Check_Response( array(
|
||||
'key' => $request->get_param( 'key' ),
|
||||
'ver' => filter_input( INPUT_GET, 'ver' ), // must come in as url param
|
||||
) );
|
||||
$response->header( 'X-As3cf-Signature', $response->hashed_signature() );
|
||||
|
||||
return $this->rest_ensure_response( 'get', static::name(), $response );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a URL to the domain check route, with the given key.
|
||||
*
|
||||
* @param string $key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_url( string $key ): string {
|
||||
return rest_url( static::endpoint() . $key );
|
||||
}
|
||||
}
|
||||
76
classes/pro/integrations/assets/api/v1/assets-settings.php
Normal file
76
classes/pro/integrations/assets/api/v1/assets-settings.php
Normal file
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations\Assets\API\V1;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\API\V1\Settings;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Integrations\Assets\Assets;
|
||||
|
||||
class Assets_Settings extends Settings {
|
||||
/** @var int */
|
||||
protected static $version = 1;
|
||||
|
||||
/** @var string */
|
||||
protected static $name = 'assets-settings';
|
||||
|
||||
/**
|
||||
* Common response values for this API endpoint.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function common_response(): array {
|
||||
/** @var Assets|null */
|
||||
$assets = $this->as3cf->get_integration_manager()->get_integration( 'assets' );
|
||||
|
||||
return array(
|
||||
'assets_settings' => $assets->obfuscate_sensitive_settings( $assets->get_all_settings() ),
|
||||
'assets_defined_settings' => array_keys( $assets->get_defined_settings() ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle saving settings submitted by user.
|
||||
*
|
||||
* @param array $new_settings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function save_settings( array $new_settings ): array {
|
||||
$changed_keys = array();
|
||||
|
||||
do_action( 'as3cf_pre_save_assets_settings' );
|
||||
|
||||
/** @var Assets $assets */
|
||||
$assets = $this->as3cf->get_integration_manager()->get_integration( 'assets' );
|
||||
|
||||
$allowed = $assets->get_allowed_settings_keys();
|
||||
$old_settings = $assets->get_all_settings( false );
|
||||
|
||||
// Merge in defined settings as they take precedence and must overwrite anything supplied.
|
||||
// Only needed to allow for validation, during save defined settings are removed from array anyway.
|
||||
$new_settings = array_merge( $new_settings, $assets->get_defined_settings() );
|
||||
|
||||
foreach ( $allowed as $key ) {
|
||||
// Whether defined or not, get rid of old database setting for key.
|
||||
$assets->remove_setting( $key );
|
||||
|
||||
if ( ! isset( $new_settings[ $key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = $assets->sanitize_setting( $key, $new_settings[ $key ] );
|
||||
|
||||
$assets->set_setting( $key, $value );
|
||||
|
||||
if ( $this->setting_changed( $old_settings, $key, $value ) ) {
|
||||
$changed_keys[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
// Great success ...
|
||||
$assets->save_settings();
|
||||
|
||||
do_action( 'as3cf_post_save_assets_settings', true );
|
||||
|
||||
return $changed_keys;
|
||||
}
|
||||
}
|
||||
669
classes/pro/integrations/assets/assets.php
Normal file
669
classes/pro/integrations/assets/assets.php
Normal file
@@ -0,0 +1,669 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations\Assets;
|
||||
|
||||
use Amazon_S3_And_CloudFront_Pro;
|
||||
use AS3CF_Utils;
|
||||
use DeliciousBrains\WP_Offload_Media\API\V1\State;
|
||||
use DeliciousBrains\WP_Offload_Media\Integrations\Integration;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Integrations\Assets\API\V1\Assets_Domain_Check;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Integrations\Assets\API\V1\Assets_Settings;
|
||||
use DeliciousBrains\WP_Offload_Media\Settings\Domain_Check;
|
||||
use DeliciousBrains\WP_Offload_Media\Settings\Exceptions\Domain_Check_Exception;
|
||||
use DeliciousBrains\WP_Offload_Media\Settings\Validator_Interface;
|
||||
use DeliciousBrains\WP_Offload_Media\Settings_Interface;
|
||||
use DeliciousBrains\WP_Offload_Media\Settings_Trait;
|
||||
use DeliciousBrains\WP_Offload_Media\Settings_Validator_Trait;
|
||||
use Exception;
|
||||
use WP_Error as AS3CF_Result;
|
||||
|
||||
class Assets extends Integration implements Settings_Interface, Validator_Interface {
|
||||
use Settings_Trait;
|
||||
use Settings_Validator_Trait;
|
||||
|
||||
const SETTINGS_KEY = 'as3cf_assets_pull';
|
||||
const VALIDATOR_KEY = 'assets';
|
||||
|
||||
/**
|
||||
* @var Amazon_S3_And_CloudFront_Pro
|
||||
*/
|
||||
protected $as3cf;
|
||||
|
||||
/**
|
||||
* Cache property of whether integration is enabled.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $enabled;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $settings_constants = array(
|
||||
'AS3CF_ASSETS_PULL_SETTINGS',
|
||||
'WPOS3_ASSETS_PULL_SETTINGS',
|
||||
);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function is_installed(): bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this integration enabled?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_enabled(): bool {
|
||||
if ( is_null( $this->enabled ) ) {
|
||||
if ( parent::is_enabled() && $this->as3cf->feature_enabled( 'assets' ) ) {
|
||||
$this->enabled = true;
|
||||
} else {
|
||||
$this->enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
return $this->enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function init() {
|
||||
// UI - Common
|
||||
add_filter( 'as3cfpro_js_strings', array( $this, 'add_js_strings' ) );
|
||||
|
||||
// Don't enable the remaining hooks unless integration enabled.
|
||||
if ( ! $this->is_enabled() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// UI - Enabled
|
||||
add_filter( 'as3cfpro_js_config', array( $this, 'add_js_config' ) );
|
||||
add_filter( 'as3cf_get_docs', array( $this, 'get_docs' ) );
|
||||
|
||||
// REST-API
|
||||
add_filter( 'as3cf_api_endpoints', array( $this, 'add_api_endpoints' ) );
|
||||
add_filter(
|
||||
$this->as3cf->get_plugin_prefix() . '_api_response_get_' . State::name(),
|
||||
array( $this, 'api_get_state' )
|
||||
);
|
||||
|
||||
// Support
|
||||
add_filter( 'as3cf_diagnostic_info', array( $this, 'diagnostic_info' ) );
|
||||
|
||||
// Keep track of whether the settings we're responsible for are currently being saved.
|
||||
add_action( 'as3cf_pre_save_assets_settings', function () {
|
||||
$this->set_saving_settings( true );
|
||||
} );
|
||||
add_action( 'as3cf_post_save_assets_settings', function () {
|
||||
$this->set_saving_settings( false );
|
||||
} );
|
||||
|
||||
// Register this instance as a validator.
|
||||
$this->as3cf->validation_manager->register_validator( self::VALIDATOR_KEY, $this );
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setup() {
|
||||
// Don't enable the hooks unless integration and URL rewriting enabled.
|
||||
if ( ! $this->is_enabled() || ! $this->should_rewrite_urls() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// URL Rewriting.
|
||||
add_filter( 'style_loader_src', array( $this, 'rewrite_src' ), 10, 2 );
|
||||
add_filter( 'script_loader_src', array( $this, 'rewrite_src' ), 10, 2 );
|
||||
add_filter( 'as3cf_get_asset', array( $this, 'rewrite_src' ) );
|
||||
add_filter( 'wp_resource_hints', array( $this, 'register_resource_hints' ), 10, 2 );
|
||||
}
|
||||
|
||||
/*
|
||||
* Settings Interface
|
||||
*/
|
||||
|
||||
/**
|
||||
* Accessor for a plugin setting with conditions for defaults and upgrades.
|
||||
*
|
||||
* @param string $key
|
||||
* @param mixed $default
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function get_setting( string $key, $default = '' ) {
|
||||
$settings = $this->get_settings();
|
||||
|
||||
$value = isset( $settings[ $key ] ) ? $settings[ $key ] : $default;
|
||||
|
||||
if ( 'rewrite-urls' == $key && ! isset( $settings[ $key ] ) ) {
|
||||
$value = false;
|
||||
}
|
||||
|
||||
if ( 'force-https' == $key && ! isset( $settings[ $key ] ) ) {
|
||||
$value = false;
|
||||
}
|
||||
|
||||
return apply_filters( 'as3cf_assets_pull_setting_' . $key, $value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Allowed settings keys for this plugin.
|
||||
*
|
||||
* @param bool $include_legacy Should legacy keys be included? Optional, default false.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_allowed_settings_keys( bool $include_legacy = false ): array {
|
||||
return array(
|
||||
'rewrite-urls',
|
||||
'domain',
|
||||
'force-https',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function get_sensitive_settings(): array {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function get_monitored_settings_blacklist(): array {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function get_skip_sanitize_settings(): array {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function get_path_format_settings(): array {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function get_prefix_format_settings(): array {
|
||||
return array();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function get_boolean_format_settings(): array {
|
||||
return array(
|
||||
'rewrite-urls',
|
||||
'force-https',
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* UI
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add additional translated strings for integration.
|
||||
*
|
||||
* @param array $strings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_js_strings( array $strings ): array {
|
||||
return array_merge( $strings, array(
|
||||
'assets_panel_header' => _x( 'Assets Pull', 'Assets panel title', 'amazon-s3-and-cloudfront' ),
|
||||
'assets_panel_header_details' => _x( 'Deliver scripts, styles, fonts and other assets from a content delivery network.', 'Assets panel details', 'amazon-s3-and-cloudfront' ),
|
||||
'assets_rewrite_urls' => _x( 'Rewrite Asset URLs', 'Setting title', 'amazon-s3-and-cloudfront' ),
|
||||
'assets_rewrite_urls_desc' => _x( 'Change the URLs of any enqueued asset files to use a CDN domain.', 'Setting description', 'amazon-s3-and-cloudfront' ),
|
||||
'assets_force_https' => _x( 'Force HTTPS', 'Setting title', 'amazon-s3-and-cloudfront' ),
|
||||
'assets_force_https_desc' => _x( 'Uses HTTPS for every rewritten asset URL instead of using the scheme of the current page.', 'Setting description', 'amazon-s3-and-cloudfront' ),
|
||||
'assets_domain_same_as_site' => __( "Domain cannot be the same as the site's domain; use a subdomain instead.", 'amazon-s3-and-cloudfront' ),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional config for integration.
|
||||
*
|
||||
* @param array $config
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_js_config( array $config ): array {
|
||||
return array_merge(
|
||||
$config,
|
||||
$this->as3cf->get_api_manager()->get_api_endpoint(
|
||||
Assets_Settings::name()
|
||||
)->common_response()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Add doc links for this integration.
|
||||
*
|
||||
* @handles as3cf_docs_data
|
||||
*
|
||||
* @param array $docs_data
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_docs( array $docs_data ): array {
|
||||
$docs_data['assets-pull'] = array(
|
||||
'url' => $this->as3cf::dbrains_url( '/wp-offload-media/doc/assets-quick-start-guide', array( 'utm_campaign' => 'WP+Offload+S3', 'assets+doc' => 'assets-tab' ) ),
|
||||
'desc' => _x( 'Click to view help doc on our site', 'Help icon alt text', 'amazon-s3-and-cloudfront' ),
|
||||
);
|
||||
|
||||
return $docs_data;
|
||||
}
|
||||
|
||||
/*
|
||||
* REST-API
|
||||
*/
|
||||
|
||||
/**
|
||||
* Add API endpoints.
|
||||
*
|
||||
* @handles as3cf_api_endpoints
|
||||
*
|
||||
* @param array $api_endpoints
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_api_endpoints( array $api_endpoints ): array {
|
||||
return array_merge( $api_endpoints, array(
|
||||
Assets_Domain_Check::name() => new Assets_Domain_Check( $this->as3cf ),
|
||||
Assets_Settings::name() => new Assets_Settings( $this->as3cf ),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional config into state API responses.
|
||||
*
|
||||
* @param array $response
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function api_get_state( array $response ): array {
|
||||
return array_merge(
|
||||
$response,
|
||||
$this->as3cf->get_api_manager()->get_api_endpoint(
|
||||
Assets_Settings::name()
|
||||
)->common_response()
|
||||
);
|
||||
}
|
||||
|
||||
/*
|
||||
* Support
|
||||
*/
|
||||
|
||||
/**
|
||||
* Integration specific diagnostic info.
|
||||
*
|
||||
* @param string $output
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function diagnostic_info( string $output = '' ): string {
|
||||
$output .= 'Assets Pull:';
|
||||
$output .= "\r\n";
|
||||
|
||||
$output .= 'Rewrite URLs: ';
|
||||
$output .= $this->on_off( 'rewrite-urls' );
|
||||
$output .= "\r\n";
|
||||
|
||||
$output .= 'Domain: ';
|
||||
$output .= esc_html( $this->get_setting( 'domain' ) );
|
||||
$output .= "\r\n";
|
||||
|
||||
$output .= 'Force HTTPS: ';
|
||||
$output .= $this->on_off( 'force-https' );
|
||||
$output .= "\r\n";
|
||||
|
||||
$output .= 'Domain Check: ';
|
||||
$output .= $this->diagnostic_domain_check();
|
||||
$output .= "\r\n\r\n";
|
||||
|
||||
$output .= 'AS3CF_ASSETS_PULL_SETTINGS: ';
|
||||
|
||||
$settings_constant = static::settings_constant();
|
||||
|
||||
if ( $settings_constant ) {
|
||||
$output .= 'Defined';
|
||||
|
||||
if ( 'AS3CF_ASSETS_PULL_SETTINGS' !== $settings_constant ) {
|
||||
$output .= ' (using ' . $settings_constant . ')';
|
||||
}
|
||||
|
||||
$defined_settings = $this->get_defined_settings();
|
||||
if ( empty( $defined_settings ) ) {
|
||||
$output .= ' - *EMPTY*';
|
||||
} else {
|
||||
$output .= "\r\n";
|
||||
$output .= 'AS3CF_ASSETS_PULL_SETTINGS Keys: ' . implode( ', ', array_keys( $defined_settings ) );
|
||||
}
|
||||
} else {
|
||||
$output .= 'Not defined';
|
||||
}
|
||||
$output .= "\r\n";
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a domain check on the configured domain for the diagnostic information.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function diagnostic_domain_check(): string {
|
||||
$domain = $this->get_setting( 'domain' );
|
||||
|
||||
if ( empty( $domain ) ) {
|
||||
return '(no domain)';
|
||||
}
|
||||
|
||||
$check = new Domain_Check( $domain );
|
||||
|
||||
try {
|
||||
$this->run_domain_check( $check );
|
||||
} catch ( Exception $e ) {
|
||||
return $e->getMessage();
|
||||
}
|
||||
|
||||
return 'OK';
|
||||
}
|
||||
|
||||
/*
|
||||
* Domain Check
|
||||
*/
|
||||
|
||||
/**
|
||||
* Check given domain is configured correctly.
|
||||
*
|
||||
* @param string $domain
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function check_domain( string $domain ): array {
|
||||
$check = new Domain_Check( $domain );
|
||||
|
||||
try {
|
||||
$this->run_domain_check( $check );
|
||||
} catch ( Exception $e ) {
|
||||
return array(
|
||||
'success' => false,
|
||||
'domain' => $check->domain(),
|
||||
'message' => _x( 'Assets cannot be delivered from the CDN.', 'Assets domain check error', 'amazon-s3-and-cloudfront' ),
|
||||
'error' => $e->getMessage(),
|
||||
'link' => $this->domain_check_more_info_link( $e ),
|
||||
'timestamp' => current_time( 'timestamp' ),
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'success' => true,
|
||||
'domain' => $check->domain(),
|
||||
'message' => _x( 'Assets are serving from the CDN with the configured domain name.', 'Assets domain check success for active domain', 'amazon-s3-and-cloudfront' ),
|
||||
'timestamp' => current_time( 'timestamp' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a "More info" link for a domain check error message.
|
||||
*
|
||||
* @param string|Exception $message
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function domain_check_more_info_link( $message ): string {
|
||||
$exception = $message;
|
||||
|
||||
if ( $message instanceof Exception ) {
|
||||
$message = $exception->getMessage();
|
||||
}
|
||||
|
||||
$utm_content = 'assets+domain+check';
|
||||
|
||||
if ( $exception instanceof Domain_Check_Exception ) {
|
||||
return $this->as3cf->more_info_link( $exception->more_info(), $utm_content, $exception->get_key() );
|
||||
}
|
||||
|
||||
// Fall-back to a search of the docs.
|
||||
return $this->as3cf->more_info_link( '/wp-offload-media/docs/?swpquery=' . urlencode( $message ), $utm_content );
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given domain check.
|
||||
*
|
||||
* @param Domain_Check $check
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function run_domain_check( Domain_Check $check ) {
|
||||
$test_time = microtime();
|
||||
$test_key = base64_encode( $test_time ); //phpcs:ignore
|
||||
|
||||
$this->test_assets_endpoint( $check, $test_key, $test_time );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a request to the test endpoint and make assertions about the response.
|
||||
*
|
||||
* @param Domain_Check $check
|
||||
* @param string $key
|
||||
* @param string $ver
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function test_assets_endpoint( Domain_Check $check, string $key, string $ver ) {
|
||||
/** @var Assets_Domain_Check $domain_check */
|
||||
$domain_check = $this->as3cf->get_api_manager()->get_api_endpoint(
|
||||
Assets_Domain_Check::name()
|
||||
);
|
||||
|
||||
$test_endpoint = $domain_check->get_url( $key );
|
||||
$test_endpoint = add_query_arg( compact( 'ver' ), $test_endpoint );
|
||||
$test_endpoint = $this->rewrite_url( $test_endpoint );
|
||||
|
||||
$response = $check->test_rest_endpoint( $test_endpoint );
|
||||
|
||||
$expected = new Domain_Check_Response( compact( 'key', 'ver' ) );
|
||||
$expected->verify_signature( wp_remote_retrieve_header( $response, 'x-as3cf-signature' ) );
|
||||
}
|
||||
|
||||
/*
|
||||
* URL Rewriting
|
||||
*/
|
||||
|
||||
/**
|
||||
* Should asset URL rewriting be performed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_rewrite_urls(): bool {
|
||||
// TODO: cache result and reuse.
|
||||
|
||||
if ( ! $this->get_setting( 'rewrite-urls' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (
|
||||
! Domain_Check::is_valid( $this->get_setting( 'domain' ) ) ||
|
||||
$this->as3cf->validation_manager->section_has_error( self::VALIDATOR_KEY )
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( is_admin() && ! AS3CF_Utils::is_ajax() && ! AS3CF_Utils::is_rest_api() ) {
|
||||
/**
|
||||
* If you're really brave, you can have Assets Pull also rewrite enqueued assets
|
||||
* within the WordPress admin dashboard.
|
||||
*
|
||||
* @param bool $rewrite
|
||||
*/
|
||||
return apply_filters( 'as3cf_assets_enable_wp_admin_rewrite', false );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the given asset URL be rewritten?
|
||||
*
|
||||
* @param mixed $src The asset URL to be rewritten.
|
||||
* @param string|null $handle The asset's registered handle in the WordPress enqueue system.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function should_rewrite_src( $src, string $handle = null ): bool {
|
||||
// If there is no string to rewrite, the answer is definitely no.
|
||||
if ( empty( $src ) || ! is_string( $src ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( AS3CF_Utils::is_relative_url( $src ) ) {
|
||||
$rewrite = true;
|
||||
} elseif ( AS3CF_Utils::url_domains_match( $src, home_url() ) ) {
|
||||
$rewrite = true;
|
||||
} else {
|
||||
$rewrite = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param bool $rewrite Should the src be rewritten?
|
||||
* @param string $src The asset URL to be rewritten.
|
||||
* @param string|null $handle The asset's registered handle in the WordPress enqueue system.
|
||||
*/
|
||||
return apply_filters( 'as3cf_assets_should_rewrite_src', $rewrite, $src, $handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite an asset's src.
|
||||
*
|
||||
* @param mixed $src
|
||||
* @param string|null $handle
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function rewrite_src( $src, string $handle = null ) {
|
||||
if ( empty( $src ) || ! is_string( $src ) ) {
|
||||
return $src;
|
||||
}
|
||||
|
||||
if ( ! $this->should_rewrite_urls() ) {
|
||||
return $src;
|
||||
}
|
||||
|
||||
if ( ! static::should_rewrite_src( $src, $handle ) ) {
|
||||
return $src;
|
||||
}
|
||||
|
||||
return $this->rewrite_url( $src, $handle );
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite a URL to use the asset's domain and scheme.
|
||||
*
|
||||
* @param string $url
|
||||
* @param string|null $handle
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function rewrite_url( string $url, string $handle = null ): string {
|
||||
$rewritten = 'http://' . $this->get_setting( 'domain' );
|
||||
$rewritten .= AS3CF_Utils::parse_url( $url, PHP_URL_PATH );
|
||||
$query = AS3CF_Utils::parse_url( $url, PHP_URL_QUERY );
|
||||
$rewritten .= $query ? ( '?' . $query ) : '';
|
||||
$scheme = $this->get_setting( 'force-https' ) ? 'https' : null;
|
||||
|
||||
/**
|
||||
* Adjust the URL scheme that Assets Pull is going to use for rewritten URL.
|
||||
*
|
||||
* @param string|null $scheme
|
||||
* @param string $url
|
||||
* @param string $handle
|
||||
*/
|
||||
$scheme = apply_filters( 'as3cf_assets_pull_scheme', $scheme, $url, $handle );
|
||||
|
||||
return set_url_scheme( $rewritten, $scheme );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a DNS prefetch tag for the pull domain if rewriting is enabled.
|
||||
*
|
||||
* @param array $hints
|
||||
* @param string $relation_type
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function register_resource_hints( array $hints, string $relation_type ): array {
|
||||
if ( 'dns-prefetch' === $relation_type && $this->should_rewrite_urls() ) {
|
||||
$hints[] = '//' . $this->get_setting( 'domain' );
|
||||
}
|
||||
|
||||
return $hints;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate settings for Assets.
|
||||
*
|
||||
* @param bool $force Force time resource consuming or state altering tests to run.
|
||||
*
|
||||
* @return AS3CF_Result
|
||||
*/
|
||||
public function validate_settings( bool $force = false ): AS3CF_Result {
|
||||
if ( $this->get_setting( 'rewrite-urls' ) ) {
|
||||
$domain_check_result = $this->check_domain( $this->get_setting( 'domain' ) );
|
||||
|
||||
// Did the domain check fail?
|
||||
if ( ! $domain_check_result['success'] ) {
|
||||
return new AS3CF_Result(
|
||||
Validator_Interface::AS3CF_STATUS_MESSAGE_ERROR,
|
||||
sprintf(
|
||||
_x( '%1$s %2$s In the meantime local assets are being served. %3$s', 'Assets notice for domain issue', 'amazon-s3-and-cloudfront' ),
|
||||
$domain_check_result['message'],
|
||||
$domain_check_result['error'],
|
||||
$domain_check_result['link']
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// All good.
|
||||
return new AS3CF_Result(
|
||||
Validator_Interface::AS3CF_STATUS_MESSAGE_SUCCESS,
|
||||
$domain_check_result['message']
|
||||
);
|
||||
} else {
|
||||
return new AS3CF_Result(
|
||||
Validator_Interface::AS3CF_STATUS_MESSAGE_WARNING,
|
||||
sprintf(
|
||||
__(
|
||||
'Assets cannot be delivered from the CDN until <strong>Rewrite Asset URLs</strong> is enabled. In the meantime, local assets are being served. <a href="%1$s" target="_blank">View Assets Quick Start Guide</a>',
|
||||
'amazon-s3-and-cloudfront'
|
||||
),
|
||||
$this->as3cf::dbrains_url( '/wp-offload-media/doc/assets-quick-start-guide', array( 'utm_campaign' => 'WP+Offload+S3', 'assets+doc' => 'assets-tab' ) )
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the name of the actions that are fired when the settings that the validator
|
||||
* is responsible for are saved.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function post_save_settings_actions(): array {
|
||||
return array( 'as3cf_post_save_assets_settings' );
|
||||
}
|
||||
}
|
||||
43
classes/pro/integrations/assets/domain-check-response.php
Normal file
43
classes/pro/integrations/assets/domain-check-response.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations\Assets;
|
||||
|
||||
use AS3CF_Utils;
|
||||
use DeliciousBrains\WP_Offload_Media\Settings\Exceptions\Signature_Verification_Exception;
|
||||
use WP_REST_Response;
|
||||
|
||||
class Domain_Check_Response extends WP_REST_Response {
|
||||
|
||||
/**
|
||||
* Verify that this response is valid for the given hashed signature.
|
||||
*
|
||||
* @param string $signature A hashed signature to verify this response against.
|
||||
*
|
||||
* @throws Signature_Verification_Exception
|
||||
*/
|
||||
public function verify_signature( string $signature ) {
|
||||
if ( ! wp_check_password( $this->raw_signature(), $signature ) ) {
|
||||
throw new Signature_Verification_Exception(
|
||||
__( 'Invalid request signature.', 'amazon-s3-and-cloudfront' )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the hashed signature for this response.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function hashed_signature(): string {
|
||||
return wp_hash_password( $this->raw_signature() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the raw signature for this response.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function raw_signature(): string {
|
||||
return AS3CF_Utils::reduce_url( network_home_url() ) . '|' . json_encode( $this->jsonSerialize() ) . '|' . AUTH_SALT;
|
||||
}
|
||||
}
|
||||
72
classes/pro/integrations/buddyboss/bboss-group-avatar.php
Normal file
72
classes/pro/integrations/buddyboss/bboss-group-avatar.php
Normal file
@@ -0,0 +1,72 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations\BuddyBoss;
|
||||
|
||||
class BBoss_Group_Avatar extends BBoss_Item {
|
||||
/**
|
||||
* Source type name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_type_name = 'Buddy Boss Group Avatar';
|
||||
|
||||
/**
|
||||
* Internal source type identifier
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_type = 'bboss-group-avatar';
|
||||
|
||||
/**
|
||||
* Table (if any) that corresponds to this source type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_table = 'bp_groups';
|
||||
|
||||
/**
|
||||
* Foreign key (if any) in the $source_table
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_fk = 'id';
|
||||
|
||||
/**
|
||||
* Relative folder where group avatars are stored on disk
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $folder = 'group-avatars';
|
||||
|
||||
/**
|
||||
* The sprintf() pattern for creating prefix based on source_id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $prefix_pattern = 'group-avatars/%d';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $is_group = true;
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
$url = self_admin_url( 'admin.php?page=bp-groups&action=edit&gid=' . $error->source_id );
|
||||
|
||||
if ( empty( $url ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (object) array(
|
||||
'url' => $url,
|
||||
'text' => __( 'Edit', 'amazon-s3-and-cloudfront' ),
|
||||
);
|
||||
}
|
||||
}
|
||||
68
classes/pro/integrations/buddyboss/bboss-group-cover.php
Normal file
68
classes/pro/integrations/buddyboss/bboss-group-cover.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations\BuddyBoss;
|
||||
|
||||
class BBoss_Group_Cover extends BBoss_Item {
|
||||
/**
|
||||
* Source type name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_type_name = 'Buddy Boss Group Cover';
|
||||
|
||||
/**
|
||||
* Internal source type identifier
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_type = 'bboss-group-cover';
|
||||
|
||||
/**
|
||||
* Table (if any) that corresponds to this source type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_table = 'bp_groups';
|
||||
|
||||
/**
|
||||
* Foreign key (if any) in the $source_table
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_fk = 'id';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $is_cover = true;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $is_group = true;
|
||||
|
||||
/**
|
||||
* Relative folder where group covers are stored on disk
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $folder = 'buddypress/groups';
|
||||
|
||||
/**
|
||||
* The sprintf() pattern for creating prefix based on source_id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $prefix_pattern = 'buddypress/groups/%d/cover-image';
|
||||
|
||||
/**
|
||||
* 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 BBoss_Group_Avatar::admin_link( $error );
|
||||
}
|
||||
}
|
||||
461
classes/pro/integrations/buddyboss/bboss-item.php
Normal file
461
classes/pro/integrations/buddyboss/bboss-item.php
Normal file
@@ -0,0 +1,461 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations\BuddyBoss;
|
||||
|
||||
use Amazon_S3_And_CloudFront;
|
||||
use AS3CF_Utils;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use DirectoryIterator;
|
||||
use WP_Error;
|
||||
|
||||
class BBoss_Item extends Item {
|
||||
/**
|
||||
* Buddy Boss images have random and unique names, object versioning not needed
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
const CAN_USE_OBJECT_VERSIONING = false;
|
||||
|
||||
/**
|
||||
* Item's summary type name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $summary_type_name = 'BuddyBoss';
|
||||
|
||||
/**
|
||||
* Item's summary type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $summary_type = 'bboss';
|
||||
|
||||
/**
|
||||
* The sprintf() pattern for creating prefix based on source_id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $prefix_pattern = '';
|
||||
|
||||
/**
|
||||
* Buddy Boss images are not managed in yearmonth folders
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
protected static $can_use_yearmonth = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $folder = '';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $is_cover = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $is_group = false;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private static $chunk_size = 1000;
|
||||
|
||||
/**
|
||||
* Get a Buddy Boss item object from the database
|
||||
*
|
||||
* @param int $source_id
|
||||
* @param string $object_type
|
||||
* @param string $image_type
|
||||
*
|
||||
* @return BBoss_Item|false
|
||||
*/
|
||||
public static function get_buddy_boss_item( $source_id, $object_type, $image_type ) {
|
||||
/** @var BBoss_Item $class */
|
||||
$class = static::get_item_class( $object_type, $image_type );
|
||||
|
||||
if ( ! empty( $class ) ) {
|
||||
return $class::get_by_source_id( $source_id );
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the appropriate Buddy Boss item sub class based on object and image type
|
||||
*
|
||||
* @param string $object_type user or group
|
||||
* @param string $image_type avatar or cover image
|
||||
*
|
||||
* @return false|string
|
||||
*/
|
||||
public static function get_item_class( $object_type, $image_type ) {
|
||||
$class_map = array(
|
||||
'user' => array(
|
||||
'avatar' => 'BBoss_User_Avatar',
|
||||
'cover' => 'BBoss_User_Cover',
|
||||
),
|
||||
'group' => array(
|
||||
'avatar' => 'BBoss_Group_Avatar',
|
||||
'cover' => 'BBoss_Group_Cover',
|
||||
),
|
||||
);
|
||||
|
||||
if ( isset( $class_map[ $object_type ][ $image_type ] ) ) {
|
||||
return __NAMESPACE__ . '\\' . $class_map[ $object_type ][ $image_type ];
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new Buddy Boss item from the source id.
|
||||
*
|
||||
* @param int $source_id
|
||||
* @param array $options
|
||||
*
|
||||
* @return BBoss_Item|WP_Error
|
||||
*/
|
||||
public static function create_from_source_id( $source_id, $options = array() ) {
|
||||
$file_paths = static::get_local_files( $source_id );
|
||||
if ( empty( $file_paths ) ) {
|
||||
return new WP_Error(
|
||||
'exception',
|
||||
__( 'No local files found in ' . __FUNCTION__, 'amazon-s3-and-cloudfront' )
|
||||
);
|
||||
}
|
||||
|
||||
$file_path = static::remove_size_from_filename( $file_paths[ Item::primary_object_key() ] );
|
||||
|
||||
$extra_info = array( 'objects' => array() );
|
||||
foreach ( $file_paths as $key => $path ) {
|
||||
$extra_info['objects'][ $key ] = array(
|
||||
'source_file' => wp_basename( $path ),
|
||||
'is_private' => false,
|
||||
);
|
||||
}
|
||||
|
||||
return new static(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
false,
|
||||
$source_id,
|
||||
$file_path,
|
||||
null,
|
||||
$extra_info,
|
||||
self::CAN_USE_OBJECT_VERSIONING
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get item's new public prefix path for current settings.
|
||||
*
|
||||
* @param bool $use_object_versioning
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_new_item_prefix( $use_object_versioning = true ) {
|
||||
/** @var Amazon_S3_And_CloudFront $as3cf */
|
||||
global $as3cf;
|
||||
|
||||
// Base prefix from settings
|
||||
$prefix = $as3cf->get_object_prefix();
|
||||
$prefix .= AS3CF_Utils::trailingslash_prefix( $as3cf->get_dynamic_prefix( null, static::$can_use_yearmonth ) );
|
||||
|
||||
// Buddy Boss specific prefix
|
||||
$buddy_boss_prefix = sprintf( static::$prefix_pattern, $this->source_id() );
|
||||
$prefix .= AS3CF_Utils::trailingslash_prefix( $buddy_boss_prefix );
|
||||
|
||||
return $prefix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all buddy boss file sizes from the source folder
|
||||
*
|
||||
* @param int $source_id
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_local_files( $source_id ) {
|
||||
$basedir = bp_core_get_upload_dir( 'upload_path' );
|
||||
|
||||
// Get base path and apply filters
|
||||
if ( static::$is_cover ) {
|
||||
// Call filters indirectly via bp_attachments_cover_image_upload_dir()
|
||||
$args = array(
|
||||
'object_id' => $source_id,
|
||||
'object_directory' => str_replace( 'buddypress/', '', static::$folder ),
|
||||
);
|
||||
$upload_dir = bp_attachments_cover_image_upload_dir( $args );
|
||||
$image_path = $upload_dir['path'];
|
||||
} else {
|
||||
// Call apply_filters directly
|
||||
$image_path = trailingslashit( $basedir ) . trailingslashit( static::$folder ) . $source_id;
|
||||
$object_type = static::$is_group ? 'group' : 'user';
|
||||
$image_path = apply_filters(
|
||||
'bp_core_avatar_folder_dir',
|
||||
$image_path,
|
||||
$source_id,
|
||||
$object_type,
|
||||
static::$folder
|
||||
);
|
||||
}
|
||||
|
||||
$result = array();
|
||||
|
||||
if ( ! file_exists( $image_path ) ) {
|
||||
return $result;
|
||||
}
|
||||
|
||||
$files = new DirectoryIterator( $image_path );
|
||||
|
||||
foreach ( $files as $file ) {
|
||||
if ( $file->isDot() ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$base_name = $file->getFilename();
|
||||
$file_name = substr( $file->getPathname(), strlen( $basedir ) );
|
||||
$file_name = AS3CF_Utils::unleadingslashit( $file_name );
|
||||
|
||||
if ( false !== strpos( $base_name, '-bp-cover-image' ) ) {
|
||||
$result[ Item::primary_object_key() ] = $file_name;
|
||||
}
|
||||
if ( false !== strpos( $base_name, '-bpfull' ) ) {
|
||||
$result[ Item::primary_object_key() ] = $file_name;
|
||||
}
|
||||
if ( false !== strpos( $base_name, '-bpthumb' ) ) {
|
||||
$result['thumb'] = $file_name;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Buddy Boss specific size removal from URL and convert it to a neutral
|
||||
* (mock) file name with the correct file extension
|
||||
*
|
||||
* @param string $file_name The file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function remove_size_from_filename( $file_name ) {
|
||||
$path_info = pathinfo( $file_name );
|
||||
|
||||
return trailingslashit( $path_info['dirname'] ) . 'bp.' . $path_info['extension'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return size name based on the file name
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return string | null
|
||||
*/
|
||||
public function get_object_key_from_filename( $filename ) {
|
||||
return BuddyBoss::get_object_key_from_filename( $filename );
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
|
||||
// Bail out with empty values if we are a group class and the groups component is not active
|
||||
if ( static::$is_group ) {
|
||||
$active_bp_components = apply_filters( 'bp_active_components', bp_get_option( 'bp-active-components' ) );
|
||||
if ( empty( $active_bp_components['groups'] ) ) {
|
||||
return $count ? 0 : array();
|
||||
}
|
||||
}
|
||||
|
||||
$source_table = $wpdb->prefix . static::$source_table;
|
||||
$basedir = bp_core_get_upload_dir( 'upload_path' );
|
||||
$dir = trailingslashit( $basedir ) . static::$folder . '/';
|
||||
$missing_items = array();
|
||||
$missing_count = 0;
|
||||
|
||||
// Establish an upper bound if needed
|
||||
if ( empty( $upper_bound ) ) {
|
||||
$sql = "SELECT max(id) from $source_table";
|
||||
$max_id = (int) $wpdb->get_var( $sql );
|
||||
$upper_bound = $max_id + 1;
|
||||
}
|
||||
|
||||
for ( $i = $upper_bound; $i >= 0; $i -= self::$chunk_size ) {
|
||||
$args = array();
|
||||
$sql = "
|
||||
SELECT t.id as ID from $source_table as t
|
||||
LEFT OUTER JOIN " . static::items_table() . " as i
|
||||
ON (i.source_id = t.ID AND i.source_type=%s)";
|
||||
$args[] = static::source_type();
|
||||
|
||||
$sql .= ' WHERE i.ID IS NULL AND t.id < %d';
|
||||
$args[] = $upper_bound;
|
||||
$sql .= ' ORDER BY t.ID DESC LIMIT %d, %d';
|
||||
$args[] = $upper_bound - $i;
|
||||
$args[] = self::$chunk_size;
|
||||
$sql = $wpdb->prepare( $sql, $args );
|
||||
|
||||
$items_without_managed_offload = array_map( 'intval', $wpdb->get_col( $sql ) );
|
||||
|
||||
foreach ( $items_without_managed_offload as $item_without_managed_offload ) {
|
||||
$target = $dir . $item_without_managed_offload;
|
||||
if ( is_dir( $target ) ) {
|
||||
if ( $count ) {
|
||||
$missing_count++;
|
||||
} else {
|
||||
$missing_items[] = $item_without_managed_offload;
|
||||
|
||||
// If we have enough items, bail out
|
||||
if ( count( $missing_items ) >= $limit ) {
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add custom default if available for offload.
|
||||
if ( ( $count || count( $missing_items ) < $limit ) && is_dir( $dir . '0' ) ) {
|
||||
if ( ! static::get_by_source_id( 0 ) && ! empty( static::get_local_files( 0 ) ) ) {
|
||||
if ( $count ) {
|
||||
$missing_count++;
|
||||
} else {
|
||||
$missing_items[] = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( $count ) {
|
||||
return $missing_count;
|
||||
} else {
|
||||
return $missing_items;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for item's path & original path values
|
||||
*
|
||||
* @param string $path
|
||||
*/
|
||||
public function set_path( $path ) {
|
||||
$path = static::remove_size_from_filename( $path );
|
||||
parent::set_path( $path );
|
||||
parent::set_original_path( $path );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get absolute source file paths for offloaded files.
|
||||
*
|
||||
* @return array Associative array of object_key => path
|
||||
*/
|
||||
public function full_source_paths() {
|
||||
$basedir = bp_core_get_upload_dir( 'upload_path' );
|
||||
$item_folder = dirname( $this->source_path() );
|
||||
|
||||
$objects = $this->objects();
|
||||
$sizes = array();
|
||||
foreach ( $objects as $size => $object ) {
|
||||
$sizes[ $size ] = trailingslashit( $basedir ) . trailingslashit( $item_folder ) . $object['source_file'];
|
||||
}
|
||||
|
||||
return $sizes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local URL for an item
|
||||
*
|
||||
* @param string|null $object_key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_local_url( $object_key = null ) {
|
||||
if ( static::$is_cover ) {
|
||||
return $this->get_local_cover_url( $object_key );
|
||||
} else {
|
||||
return $this->get_local_avatar_url( $object_key );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local URL for an avatar item
|
||||
*
|
||||
* @param string|null $object_key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_local_avatar_url( $object_key = null ) {
|
||||
$uploads = wp_upload_dir();
|
||||
$url = trailingslashit( $uploads['baseurl'] );
|
||||
$url .= $this->source_path( $object_key );
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local URL for a cover item
|
||||
*
|
||||
* @param string|null $object_key
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_local_cover_url( $object_key = null ) {
|
||||
$uploads = wp_upload_dir();
|
||||
$url = trailingslashit( $uploads['baseurl'] );
|
||||
$url .= $this->source_path( $object_key );
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the prefix pattern
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_prefix_pattern() {
|
||||
return static::$prefix_pattern;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 ' . static::items_table() . ' WHERE source_type = %s';
|
||||
$sql = $wpdb->prepare( $sql, static::$source_type );
|
||||
$offloaded_count = (int) $wpdb->get_var( $sql );
|
||||
$missing_count = static::get_missing_source_ids( 0, 0, true );
|
||||
|
||||
if ( is_array( $missing_count ) ) {
|
||||
$missing_count = count( $missing_count );
|
||||
}
|
||||
|
||||
return array(
|
||||
'total' => $offloaded_count + $missing_count,
|
||||
'offloaded' => $offloaded_count,
|
||||
'not_offloaded' => $missing_count,
|
||||
);
|
||||
}
|
||||
}
|
||||
67
classes/pro/integrations/buddyboss/bboss-user-avatar.php
Normal file
67
classes/pro/integrations/buddyboss/bboss-user-avatar.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations\BuddyBoss;
|
||||
|
||||
class BBoss_User_Avatar extends BBoss_Item {
|
||||
/**
|
||||
* Source type name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_type_name = 'Buddy Boss User Avatar';
|
||||
|
||||
/**
|
||||
* Internal source type identifier
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_type = 'bboss-user-avatar';
|
||||
|
||||
/**
|
||||
* Table (if any) that corresponds to this source type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_table = 'users';
|
||||
|
||||
/**
|
||||
* Foreign key (if any) in the $source_table
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_fk = 'id';
|
||||
|
||||
/**
|
||||
* Relative folder where user avatars are stored on disk
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $folder = 'avatars';
|
||||
|
||||
/**
|
||||
* The sprintf() pattern for creating prefix based on source_id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $prefix_pattern = 'avatars/%d';
|
||||
|
||||
/**
|
||||
* 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 ) {
|
||||
$url = self_admin_url( 'users.php?page=bp-profile-edit&user_id=' . $error->source_id );
|
||||
|
||||
if ( empty( $url ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (object) array(
|
||||
'url' => $url,
|
||||
'text' => __( 'Edit', 'amazon-s3-and-cloudfront' ),
|
||||
);
|
||||
}
|
||||
}
|
||||
63
classes/pro/integrations/buddyboss/bboss-user-cover.php
Normal file
63
classes/pro/integrations/buddyboss/bboss-user-cover.php
Normal file
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations\BuddyBoss;
|
||||
|
||||
class BBoss_User_Cover extends BBoss_Item {
|
||||
/**
|
||||
* Source type name
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_type_name = 'Buddy Boss User Cover';
|
||||
|
||||
/**
|
||||
* Internal source type identifier
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_type = 'bboss-user-cover';
|
||||
|
||||
/**
|
||||
* Table (if any) that corresponds to this source type
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_table = 'users';
|
||||
|
||||
/**
|
||||
* Foreign key (if any) in the $source_table
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $source_fk = 'id';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $is_cover = true;
|
||||
|
||||
/**
|
||||
* Relative folder where user covers are stored on disk
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $folder = 'buddypress/members';
|
||||
|
||||
/**
|
||||
* The sprintf() pattern for creating prefix based on source_id.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $prefix_pattern = 'buddypress/members/%d/cover-image';
|
||||
|
||||
/**
|
||||
* 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 BBoss_User_Avatar::admin_link( $error );
|
||||
}
|
||||
}
|
||||
769
classes/pro/integrations/buddyboss/buddyboss.php
Normal file
769
classes/pro/integrations/buddyboss/buddyboss.php
Normal file
@@ -0,0 +1,769 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations\BuddyBoss;
|
||||
|
||||
use AS3CF_Error;
|
||||
use AS3CF_Utils;
|
||||
use DeliciousBrains\WP_Offload_Media\Integrations\Integration;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Download_Handler;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Upload_Handler;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Items\Remove_Provider_Handler;
|
||||
use Exception;
|
||||
|
||||
class BuddyBoss extends Integration {
|
||||
/**
|
||||
* Our item types
|
||||
*
|
||||
* @var object[]
|
||||
*/
|
||||
private $source_types;
|
||||
|
||||
/**
|
||||
* Are we inside a crop operation?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $in_crop = false;
|
||||
|
||||
/**
|
||||
* Did we handle a crop operation?
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $did_crop = false;
|
||||
|
||||
/**
|
||||
* Is installed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_installed(): bool {
|
||||
global $buddyboss_platform_plugin_file;
|
||||
|
||||
if ( class_exists( 'BuddyPress' ) && is_string( $buddyboss_platform_plugin_file ) && ! is_multisite() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init integration.
|
||||
*/
|
||||
public function init() {
|
||||
$this->source_types = array(
|
||||
'bboss-user-avatar' => array(
|
||||
'class' => BBoss_Item::get_item_class( 'user', 'avatar' ),
|
||||
),
|
||||
'bboss-user-cover' => array(
|
||||
'class' => BBoss_Item::get_item_class( 'user', 'cover' ),
|
||||
),
|
||||
'bboss-group-avatar' => array(
|
||||
'class' => BBoss_Item::get_item_class( 'group', 'avatar' ),
|
||||
),
|
||||
'bboss-group-cover' => array(
|
||||
'class' => BBoss_Item::get_item_class( 'group', 'cover' ),
|
||||
),
|
||||
);
|
||||
|
||||
// Register our item source types with the global as3cf object.
|
||||
foreach ( $this->source_types as $key => $source_type ) {
|
||||
$this->as3cf->register_source_type( $key, $source_type['class'] );
|
||||
}
|
||||
|
||||
// Register our item summary types with the global as3cf object.
|
||||
$this->as3cf->register_summary_type( BBoss_Item::summary_type(), BBoss_Item::class );
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setup() {
|
||||
// URL Rewriting.
|
||||
add_filter( 'bp_core_fetch_avatar_url_check', array( $this, 'fetch_avatar' ), 10, 2 );
|
||||
add_filter( 'bp_core_fetch_gravatar_url_check', array( $this, 'fetch_default_avatar' ), 99, 2 );
|
||||
add_filter( 'bb_get_default_custom_upload_profile_avatar', array( $this, 'filter_bb_get_default_custom_upload_profile_avatar' ), 10, 2 );
|
||||
add_filter( 'bb_get_default_custom_upload_group_avatar', array( $this, 'filter_bb_get_default_custom_upload_group_avatar' ), 10, 2 );
|
||||
add_filter( 'bp_attachments_pre_get_attachment', array( $this, 'fetch_cover' ), 10, 2 );
|
||||
add_filter( 'bb_get_default_custom_upload_profile_cover', array( $this, 'filter_bb_get_default_custom_upload_profile_cover' ), 10 );
|
||||
add_filter( 'bb_get_default_custom_upload_group_cover', array( $this, 'filter_bb_get_default_custom_upload_group_cover' ), 10 );
|
||||
|
||||
// Storage handling.
|
||||
add_action( 'bp_core_pre_avatar_handle_crop', array( $this, 'filter_bp_core_pre_avatar_handle_crop' ), 10, 2 );
|
||||
add_action( 'xprofile_avatar_uploaded', array( $this, 'avatar_uploaded' ), 10, 3 );
|
||||
add_action( 'groups_avatar_uploaded', array( $this, 'avatar_uploaded' ), 10, 3 );
|
||||
add_action( 'xprofile_cover_image_uploaded', array( $this, 'user_cover_uploaded' ), 10, 1 );
|
||||
add_action( 'groups_cover_image_uploaded', array( $this, 'groups_cover_uploaded' ), 10, 1 );
|
||||
add_action( 'bp_core_delete_existing_avatar', array( $this, 'delete_existing_avatar' ), 10, 1 );
|
||||
add_action( 'xprofile_cover_image_deleted', array( $this, 'delete_existing_user_cover' ), 10, 1 );
|
||||
add_action( 'groups_cover_image_deleted', array( $this, 'delete_existing_group_cover' ), 10, 1 );
|
||||
add_action( 'deleted_user', array( $this, 'handle_deleted_user' ), 10, 1 );
|
||||
add_action( 'groups_delete_group', array( $this, 'groups_delete_group' ), 10, 1 );
|
||||
add_filter( 'bp_attachments_pre_delete_file', array( $this, 'bp_attachments_pre_delete_file' ), 10, 2 );
|
||||
|
||||
// Internal filters.
|
||||
add_filter( 'as3cf_remove_size_from_filename', array( $this, 'remove_size_from_filename' ), 10, 1 );
|
||||
add_filter( 'as3cf_get_size_string_from_url_for_item_source', array( $this, 'get_size_string_from_url_for_item_source' ), 10, 3 );
|
||||
add_filter( 'as3cf_get_provider_url_for_item_source', array( $this, 'filter_get_provider_url_for_item_source' ), 10, 3 );
|
||||
add_filter( 'as3cf_get_local_url_for_item_source', array( $this, 'filter_get_local_url_for_item_source' ), 10, 3 );
|
||||
add_filter( 'as3cf_strip_image_edit_suffix_and_extension', array( $this, 'filter_strip_image_edit_suffix_and_extension' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* If possible, rewrite local avatar URL to remote, possibly using substitute source.
|
||||
*
|
||||
* @param string $avatar_url
|
||||
* @param array $params
|
||||
* @param null|int $source_id Optional override for the source ID, e.g. default = 0.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function rewrite_avatar_url( $avatar_url, $params, $source_id = null ) {
|
||||
if ( ! $this->as3cf->get_setting( 'serve-from-s3' ) ) {
|
||||
return $avatar_url;
|
||||
}
|
||||
|
||||
if ( ! isset( $params['item_id'] ) || ! is_numeric( $params['item_id'] ) || empty( $params['object'] ) ) {
|
||||
return $avatar_url;
|
||||
}
|
||||
|
||||
if ( ! empty( $avatar_url ) && ! $this->as3cf->filter_local->url_needs_replacing( $avatar_url ) ) {
|
||||
return $avatar_url;
|
||||
}
|
||||
|
||||
if ( ! is_numeric( $source_id ) ) {
|
||||
$source_id = $params['item_id'];
|
||||
}
|
||||
|
||||
$as3cf_item = BBoss_Item::get_buddy_boss_item( $source_id, $params['object'], 'avatar' );
|
||||
if ( false !== $as3cf_item ) {
|
||||
$image_type = ! empty( $params['type'] ) ? $params['type'] : 'full';
|
||||
$size = 'thumb' === $image_type ? 'thumb' : Item::primary_object_key();
|
||||
|
||||
$new_url = $as3cf_item->get_provider_url( $size );
|
||||
|
||||
if ( ! empty( $new_url ) && is_string( $new_url ) ) {
|
||||
return $new_url;
|
||||
}
|
||||
}
|
||||
|
||||
return $avatar_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the avatar's remote URL.
|
||||
*
|
||||
* @handles bp_core_fetch_avatar_url_check
|
||||
*
|
||||
* @param string $avatar_url
|
||||
* @param array $params
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function fetch_avatar( $avatar_url, $params ) {
|
||||
return $this->rewrite_avatar_url( $avatar_url, $params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the avatar's remote default URL if gravatar not supplied.
|
||||
*
|
||||
* @handles bp_core_fetch_gravatar_url_check
|
||||
*
|
||||
* @param string $avatar_url
|
||||
* @param array $params
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function fetch_default_avatar( $avatar_url, $params ) {
|
||||
return $this->rewrite_avatar_url( $avatar_url, $params, 0 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters to change default custom upload avatar image.
|
||||
*
|
||||
* @handles bb_get_default_custom_upload_profile_avatar
|
||||
*
|
||||
* @param string $custom_upload_profile_avatar Default custom upload avatar URL.
|
||||
* @param string $size This parameter specifies whether you'd like the 'full' or 'thumb' avatar.
|
||||
*/
|
||||
public function filter_bb_get_default_custom_upload_profile_avatar( $custom_upload_profile_avatar, $size ) {
|
||||
$params = array(
|
||||
'item_id' => 0,
|
||||
'object' => 'user',
|
||||
'type' => $size,
|
||||
);
|
||||
|
||||
return $this->rewrite_avatar_url( $custom_upload_profile_avatar, $params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters to change default custom upload avatar image.
|
||||
*
|
||||
* @handles bb_get_default_custom_upload_group_avatar
|
||||
*
|
||||
* @param string $custom_upload_group_avatar Default custom upload avatar URL.
|
||||
* @param string $size This parameter specifies whether you'd like the 'full' or 'thumb' avatar.
|
||||
*/
|
||||
public function filter_bb_get_default_custom_upload_group_avatar( $custom_upload_group_avatar, $size ) {
|
||||
$params = array(
|
||||
'item_id' => 0,
|
||||
'object' => 'group',
|
||||
'type' => $size,
|
||||
);
|
||||
|
||||
return $this->rewrite_avatar_url( $custom_upload_group_avatar, $params );
|
||||
}
|
||||
|
||||
/**
|
||||
* If possible, rewrite local cover URL to remote, possibly using substitute source.
|
||||
*
|
||||
* @param string $cover_url
|
||||
* @param array $params
|
||||
* @param null|int $source_id Optional override for the source ID, e.g. default = 0.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function rewrite_cover_url( $cover_url, $params, $source_id = null ) {
|
||||
if ( ! $this->as3cf->get_setting( 'serve-from-s3' ) ) {
|
||||
return $cover_url;
|
||||
}
|
||||
|
||||
if ( ! isset( $params['item_id'] ) || ! is_numeric( $params['item_id'] ) || empty( $params['object_dir'] ) ) {
|
||||
return $cover_url;
|
||||
}
|
||||
|
||||
$object_type = $this->object_type_from_dir( $params['object_dir'] );
|
||||
if ( is_null( $object_type ) ) {
|
||||
return $cover_url;
|
||||
}
|
||||
|
||||
if ( ! empty( $cover_url ) && ! $this->as3cf->filter_local->url_needs_replacing( $cover_url ) ) {
|
||||
return $cover_url;
|
||||
}
|
||||
|
||||
if ( ! is_numeric( $source_id ) ) {
|
||||
$source_id = $params['item_id'];
|
||||
}
|
||||
|
||||
$as3cf_item = BBoss_Item::get_buddy_boss_item( $source_id, $object_type, 'cover' );
|
||||
if ( false !== $as3cf_item ) {
|
||||
$new_url = $as3cf_item->get_provider_url( Item::primary_object_key() );
|
||||
|
||||
if ( ! empty( $new_url ) && is_string( $new_url ) ) {
|
||||
// We should not supply remote URL during a delete operation,
|
||||
// but the delete process will fail if there isn't a local file to delete.
|
||||
if ( isset( $_POST['action'] ) && 'bp_cover_image_delete' === $_POST['action'] ) {
|
||||
if ( ! $as3cf_item->exists_locally() ) {
|
||||
/** @var Download_Handler $download_handler */
|
||||
$download_handler = $this->as3cf->get_item_handler( Download_Handler::get_item_handler_key_name() );
|
||||
$download_handler->handle( $as3cf_item );
|
||||
}
|
||||
|
||||
return $cover_url;
|
||||
}
|
||||
|
||||
return $new_url;
|
||||
}
|
||||
}
|
||||
|
||||
return $cover_url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cover's remote URL.
|
||||
*
|
||||
* @handles bp_attachments_pre_get_attachment
|
||||
*
|
||||
* @param string $cover_url
|
||||
* @param array $params
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function fetch_cover( $cover_url, $params ) {
|
||||
return $this->rewrite_cover_url( $cover_url, $params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters to change default custom upload cover image.
|
||||
*
|
||||
* @handles bb_get_default_custom_upload_profile_cover
|
||||
*
|
||||
* @param string $value Default custom upload profile cover URL.
|
||||
*/
|
||||
public function filter_bb_get_default_custom_upload_profile_cover( $value ) {
|
||||
$params = array(
|
||||
'item_id' => 0,
|
||||
'object_dir' => 'members',
|
||||
);
|
||||
|
||||
return $this->rewrite_cover_url( $value, $params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters default custom upload cover image URL.
|
||||
*
|
||||
* @handles bb_get_default_custom_upload_group_cover
|
||||
*
|
||||
* @param string $value Default custom upload group cover URL.
|
||||
*/
|
||||
public function filter_bb_get_default_custom_upload_group_cover( $value ) {
|
||||
$params = array(
|
||||
'item_id' => 0,
|
||||
'object_dir' => 'groups',
|
||||
);
|
||||
|
||||
return $this->rewrite_cover_url( $value, $params );
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters whether or not to handle cropping.
|
||||
*
|
||||
* But we use it to catch a successful crop so we can offload
|
||||
* and later supply the correct remote URL.
|
||||
*
|
||||
* @handles bp_core_pre_avatar_handle_crop
|
||||
*
|
||||
* @param bool $value Whether or not to crop.
|
||||
* @param array $r Array of parsed arguments for function.
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function filter_bp_core_pre_avatar_handle_crop( $value, $r ) {
|
||||
if ( ! function_exists( 'bp_core_avatar_handle_crop' ) ) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$this->in_crop = ! $this->in_crop;
|
||||
|
||||
if ( $this->in_crop ) {
|
||||
if ( bp_core_avatar_handle_crop( $r ) ) {
|
||||
$this->avatar_uploaded( $r['item_id'], 'crop', $r );
|
||||
$this->did_crop = true;
|
||||
}
|
||||
|
||||
// We handled the crop.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't cancel operation when we call it above.
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a newly uploaded avatar
|
||||
*
|
||||
* @handles xprofile_avatar_uploaded
|
||||
* @handles groups_avatar_uploaded
|
||||
*
|
||||
* @param int $source_id
|
||||
* @param string $avatar_type
|
||||
* @param array $params
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function avatar_uploaded( $source_id, $avatar_type, $params ) {
|
||||
if ( $this->did_crop ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->as3cf->get_setting( 'copy-to-s3' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( empty( $params['object'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$object_type = $params['object'];
|
||||
$image_type = 'avatar';
|
||||
|
||||
$as3cf_item = BBoss_Item::get_buddy_boss_item( $source_id, $object_type, $image_type );
|
||||
if ( false !== $as3cf_item ) {
|
||||
$this->delete_existing_avatar( array( 'item_id' => $source_id, 'object' => $object_type ) );
|
||||
}
|
||||
|
||||
/** @var BBoss_Item $class */
|
||||
$class = BBoss_Item::get_item_class( $object_type, $image_type );
|
||||
$as3cf_item = $class::create_from_source_id( $source_id );
|
||||
|
||||
$upload_handler = $this->as3cf->get_item_handler( Upload_Handler::get_item_handler_key_name() );
|
||||
$upload_result = $upload_handler->handle( $as3cf_item );
|
||||
|
||||
// TODO: Should not be needed ...
|
||||
if ( is_wp_error( $upload_result ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: ... as this should be redundant.
|
||||
// TODO: However, when user has offloaded avatar and replaces it,
|
||||
// TODO: this save is required as handler returns false.
|
||||
// TODO: As there is a delete above, this should not be the case!
|
||||
$as3cf_item->save();
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle when a new user cover image is uploaded
|
||||
*
|
||||
* @handles xprofile_cover_image_uploaded
|
||||
*
|
||||
* @param int $source_id
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function user_cover_uploaded( $source_id ) {
|
||||
$this->cover_uploaded( $source_id, 'user' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle when a new group cover image is uploaded
|
||||
*
|
||||
* @handles xprofile_cover_image_uploaded
|
||||
*
|
||||
* @param int $source_id
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function groups_cover_uploaded( $source_id ) {
|
||||
$this->cover_uploaded( $source_id, 'group' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a new group or user cover image
|
||||
*
|
||||
* @param int $source_id
|
||||
* @param string $object_type
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function cover_uploaded( $source_id, $object_type ) {
|
||||
if ( ! $this->as3cf->get_setting( 'copy-to-s3' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$as3cf_item = BBoss_Item::get_buddy_boss_item( $source_id, $object_type, 'cover' );
|
||||
if ( false !== $as3cf_item ) {
|
||||
$this->delete_existing_cover( $source_id, $object_type );
|
||||
}
|
||||
|
||||
/** @var BBoss_Item $class */
|
||||
$class = BBoss_Item::get_item_class( $object_type, 'cover' );
|
||||
$as3cf_item = $class::create_from_source_id( $source_id );
|
||||
|
||||
$upload_handler = $this->as3cf->get_item_handler( Upload_Handler::get_item_handler_key_name() );
|
||||
$upload_handler->handle( $as3cf_item );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a user cover image from the remote bucket
|
||||
*
|
||||
* @handles xprofile_cover_image_deleted
|
||||
*
|
||||
* @param int $source_id
|
||||
*/
|
||||
public function delete_existing_user_cover( $source_id ) {
|
||||
$this->delete_existing_cover( $source_id, 'user' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a group cover image from the remote bucket
|
||||
*
|
||||
* @handles groups_cover_image_deleted
|
||||
*
|
||||
* @param int $source_id
|
||||
*/
|
||||
public function delete_existing_group_cover( $source_id ) {
|
||||
$this->delete_existing_cover( $source_id, 'group' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a cover image from the remote bucket
|
||||
*
|
||||
* @handles bp_core_delete_existing_avatar
|
||||
*
|
||||
* @param int $source_id
|
||||
* @param string $object_type
|
||||
*/
|
||||
public function delete_existing_cover( $source_id, $object_type ) {
|
||||
/** @var BBoss_Item $as3cf_item */
|
||||
$as3cf_item = BBoss_Item::get_buddy_boss_item( $source_id, $object_type, 'cover' );
|
||||
if ( ! empty( $as3cf_item ) ) {
|
||||
$remove_provider = $this->as3cf->get_item_handler( Remove_Provider_Handler::get_item_handler_key_name() );
|
||||
$remove_provider->handle( $as3cf_item, array( 'verify_exists_on_local' => false ) );
|
||||
$as3cf_item->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes avatar and cover from remote bucket when a user is deleted
|
||||
*
|
||||
* @handles deleted_user
|
||||
*
|
||||
* @param int $user_id
|
||||
*/
|
||||
public function handle_deleted_user( $user_id ) {
|
||||
$args = array( 'item_id' => $user_id, 'object' => 'user' );
|
||||
$this->delete_existing_avatar( $args );
|
||||
$this->delete_existing_cover( $user_id, 'user' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes avatar and cover when a group is deleted
|
||||
*
|
||||
* @handles groups_delete_group
|
||||
*
|
||||
* @param int $group_id
|
||||
*/
|
||||
public function groups_delete_group( $group_id ) {
|
||||
$args = array( 'item_id' => $group_id, 'object' => 'group' );
|
||||
$this->delete_existing_avatar( $args );
|
||||
$this->delete_existing_cover( $group_id, 'group' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes an avatar from the remote bucket
|
||||
*
|
||||
* @handles bp_core_delete_existing_avatar
|
||||
*
|
||||
* @param array $args
|
||||
*/
|
||||
public function delete_existing_avatar( $args ) {
|
||||
if ( ! isset( $args['item_id'] ) || ! is_numeric( $args['item_id'] ) || empty( $args['object'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @var BBoss_Item $as3cf_item */
|
||||
$as3cf_item = BBoss_Item::get_buddy_boss_item( $args['item_id'], $args['object'], 'avatar' );
|
||||
if ( ! empty( $as3cf_item ) ) {
|
||||
$remove_provider = $this->as3cf->get_item_handler( Remove_Provider_Handler::get_item_handler_key_name() );
|
||||
$remove_provider->handle( $as3cf_item, array( 'verify_exists_on_local' => false ) );
|
||||
$as3cf_item->delete();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Identifies URLs to avatars and cover images and rewrites the URL to
|
||||
* the size neutral version.
|
||||
*
|
||||
* @handles as3cf_remove_size_from_filename
|
||||
*
|
||||
* @param string $url
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function remove_size_from_filename( $url ) {
|
||||
$found_match = false;
|
||||
foreach ( $this->source_types as $source_type ) {
|
||||
/** @var BBoss_Item $class */
|
||||
$class = $source_type['class'];
|
||||
$pattern = sprintf(
|
||||
'/\/%s\/[0-9a-f]{9,14}-bp(full|thumb|\-cover\-image)\./',
|
||||
str_replace( '%d', '\d+', preg_quote( $class::get_prefix_pattern(), '/' ) )
|
||||
);
|
||||
|
||||
$match = preg_match( $pattern, $url );
|
||||
if ( ! empty( $match ) ) {
|
||||
$found_match = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $found_match ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
return BBoss_Item::remove_size_from_filename( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size from a URL for the Buddy Boss item types
|
||||
*
|
||||
* @handles as3cf_get_size_string_from_url_for_item_source
|
||||
*
|
||||
* @param string $size
|
||||
* @param string $url
|
||||
* @param array $item_source
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_size_string_from_url_for_item_source( $size, $url, $item_source ) {
|
||||
if ( ! in_array( $item_source['source_type'], array_keys( $this->source_types ), true ) ) {
|
||||
return $size;
|
||||
}
|
||||
|
||||
return static::get_object_key_from_filename( $url );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return size name based on the file name
|
||||
*
|
||||
* @param string $filename
|
||||
*
|
||||
* @return string | null
|
||||
*/
|
||||
public static function get_object_key_from_filename( $filename ) {
|
||||
$size = Item::primary_object_key();
|
||||
$filename = preg_replace( '/\?.*/', '', $filename );
|
||||
$basename = AS3CF_Utils::encode_filename_in_path( wp_basename( $filename ) );
|
||||
|
||||
if ( false !== strpos( $basename, '-bpthumb' ) ) {
|
||||
$size = 'thumb';
|
||||
}
|
||||
|
||||
return $size;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the remote URL for a User / Group avatar or cover image
|
||||
*
|
||||
* @handles as3cf_get_provider_url_for_item_source
|
||||
*
|
||||
* @param string $url Url
|
||||
* @param array $item_source The item source descriptor array
|
||||
* @param string $size Name of requested size
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function filter_get_provider_url_for_item_source( $url, $item_source, $size ) {
|
||||
if ( Item::is_empty_item_source( $item_source ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
if ( ! in_array( $item_source['source_type'], array_keys( $this->source_types ), true ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
/** @var BBoss_Item $class */
|
||||
$class = ! empty( $this->source_types[ $item_source['source_type'] ] ) ? $this->source_types[ $item_source['source_type'] ]['class'] : false;
|
||||
if ( false !== $class ) {
|
||||
if ( empty( $size ) ) {
|
||||
$size = Item::primary_object_key();
|
||||
}
|
||||
|
||||
$as3cf_item = $class::get_by_source_id( $item_source['id'] );
|
||||
if ( empty( $as3cf_item ) || ! $as3cf_item->served_by_provider() ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$url = $as3cf_item->get_provider_url( $size );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the local URL for a User / Group avatar or cover image
|
||||
*
|
||||
* @handles as3cf_get_local_url_for_item_source
|
||||
*
|
||||
* @param string $url Url
|
||||
* @param array $item_source The item source descriptor array
|
||||
* @param string $size Name of requested size
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function filter_get_local_url_for_item_source( $url, $item_source, $size ) {
|
||||
if ( Item::is_empty_item_source( $item_source ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
if ( ! in_array( $item_source['source_type'], array_keys( $this->source_types ), true ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
/** @var BBoss_Item $class */
|
||||
$class = ! empty( $this->source_types[ $item_source['source_type'] ] ) ? $this->source_types[ $item_source['source_type'] ]['class'] : false;
|
||||
if ( ! empty( $class ) ) {
|
||||
if ( empty( $size ) ) {
|
||||
$size = Item::primary_object_key();
|
||||
}
|
||||
|
||||
$as3cf_item = $class::get_by_source_id( $item_source['id'] );
|
||||
if ( empty( $as3cf_item ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$url = $as3cf_item->get_local_url( $size );
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove fake filename ending from a stripped bucket key
|
||||
*
|
||||
* @handles as3cf_strip_image_edit_suffix_and_extension
|
||||
*
|
||||
* @param string $path
|
||||
* @param string $source_type
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function filter_strip_image_edit_suffix_and_extension( $path, $source_type ) {
|
||||
if ( ! in_array( $source_type, array_keys( $this->source_types ), true ) ) {
|
||||
return $path;
|
||||
}
|
||||
|
||||
if ( '/bp' === substr( $path, -3 ) ) {
|
||||
$path = trailingslashit( dirname( $path ) );
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle / override Buddy Boss attempt to delete a local file that we have already removed
|
||||
*
|
||||
* @handles bp_attachments_pre_delete_file
|
||||
*
|
||||
* @param bool $pre
|
||||
* @param array $args
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function bp_attachments_pre_delete_file( $pre, $args ) {
|
||||
if ( empty( $args['object_dir'] ) || empty( $args['item_id'] ) ) {
|
||||
return $pre;
|
||||
}
|
||||
|
||||
$object_type = $this->object_type_from_dir( $args['object_dir'] );
|
||||
if ( is_null( $object_type ) ) {
|
||||
return $pre;
|
||||
}
|
||||
|
||||
/** @var BBoss_Item $class */
|
||||
$class = BBoss_Item::get_item_class( $object_type, 'cover' );
|
||||
$as3cf_item = $class::get_by_source_id( (int) $args['item_id'] );
|
||||
|
||||
if ( ! $as3cf_item ) {
|
||||
return $pre;
|
||||
}
|
||||
|
||||
$source_file = $as3cf_item->full_source_path( Item::primary_object_key() );
|
||||
if ( file_exists( $source_file ) ) {
|
||||
return $pre;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return object_type (user or group) based on object_dir passed in from Buddy Boss
|
||||
*
|
||||
* @param string $object_dir
|
||||
*
|
||||
* @return string|null
|
||||
*/
|
||||
private function object_type_from_dir( $object_dir ) {
|
||||
switch ( $object_dir ) {
|
||||
case 'members':
|
||||
return 'user';
|
||||
case 'groups':
|
||||
return 'group';
|
||||
}
|
||||
|
||||
AS3CF_Error::log( 'Unknown object_dir ' . $object_dir );
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
52
classes/pro/integrations/core-pro.php
Normal file
52
classes/pro/integrations/core-pro.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations;
|
||||
|
||||
use Amazon_S3_And_CloudFront_Pro;
|
||||
use DeliciousBrains\WP_Offload_Media\Integrations\Core;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Download_Handler;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Items\Remove_Provider_Handler;
|
||||
use WP_Error;
|
||||
|
||||
class Core_Pro extends Core {
|
||||
/**
|
||||
* @var Amazon_S3_And_CloudFront_Pro
|
||||
*/
|
||||
protected $as3cf;
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setup() {
|
||||
parent::setup();
|
||||
|
||||
add_action( 'as3cf_pre_handle_item_' . Remove_Provider_Handler::get_item_handler_key_name(), array( $this, 'maybe_download_files' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Before removing from provider, maybe download files.
|
||||
*
|
||||
* @handles as3cf_pre_handle_item_remove-provider
|
||||
*
|
||||
* @param bool $cancel Should the action on the item be cancelled?
|
||||
* @param Item $as3cf_item The item that the action is being handled for.
|
||||
* @param array $options Handler dependent options that may have been set for the action.
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
public function maybe_download_files( $cancel, Item $as3cf_item, array $options ) {
|
||||
if ( false === $cancel && ! empty( $options['verify_exists_on_local'] ) ) {
|
||||
$download_handler = $this->as3cf->get_item_handler( Download_Handler::get_item_handler_key_name() );
|
||||
$result = $download_handler->handle( $as3cf_item );
|
||||
|
||||
// If there was any kind of error, then remove from provider should not proceed.
|
||||
// Because this is an unexpected error, bubble it.
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
|
||||
return $cancel;
|
||||
}
|
||||
}
|
||||
129
classes/pro/integrations/divi.php
Normal file
129
classes/pro/integrations/divi.php
Normal file
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations;
|
||||
|
||||
use AS3CF_Utils;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Integrations\Integration;
|
||||
|
||||
class Divi extends Integration {
|
||||
|
||||
/**
|
||||
* Is installed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_installed(): bool {
|
||||
// This integration fixes problems introduced by Divi Page Builder as used by the Divi and related themes.
|
||||
if ( defined( 'ET_BUILDER_VERSION' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init integration.
|
||||
*/
|
||||
public function init() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setup() {
|
||||
add_filter( 'et_fb_load_raw_post_content', function ( $content ) {
|
||||
return apply_filters( 'as3cf_filter_post_local_to_provider', $content );
|
||||
} );
|
||||
|
||||
// Before attachment lookup via GUID, revert remote URL to local URL.
|
||||
add_filter( 'et_get_attachment_id_by_url_guid', function ( $url ) {
|
||||
return apply_filters( 'as3cf_filter_post_provider_to_local', $url );
|
||||
} );
|
||||
|
||||
// Global Modules reset their filtered background image URLs, so let's fix that.
|
||||
if ( defined( 'ET_BUILDER_LAYOUT_POST_TYPE' ) ) {
|
||||
add_filter( 'the_posts', array( $this, 'the_posts' ), 10, 2 );
|
||||
}
|
||||
|
||||
// The Divi Page Builder Gallery uses a non-standard and inherently anti-filter method of getting its editor thumbnails.
|
||||
if ( $this->doing_fetch_attachments() ) {
|
||||
add_action( 'pre_get_posts', array( $this, 'pre_get_posts' ) );
|
||||
}
|
||||
|
||||
// The Divi Theme Builder may need to refresh its cached CSS if media URLs possibly changed.
|
||||
if ( function_exists( 'et_core_page_resource_auto_clear' ) ) {
|
||||
add_action( 'as3cf_copy_buckets_cancelled', 'et_core_page_resource_auto_clear' );
|
||||
add_action( 'as3cf_copy_buckets_completed', 'et_core_page_resource_auto_clear' );
|
||||
add_action( 'as3cf_download_and_remover_cancelled', 'et_core_page_resource_auto_clear' );
|
||||
add_action( 'as3cf_download_and_remover_completed', 'et_core_page_resource_auto_clear' );
|
||||
add_action( 'as3cf_move_objects_cancelled', 'et_core_page_resource_auto_clear' );
|
||||
add_action( 'as3cf_move_objects_completed', 'et_core_page_resource_auto_clear' );
|
||||
add_action( 'as3cf_move_private_objects_cancelled', 'et_core_page_resource_auto_clear' );
|
||||
add_action( 'as3cf_move_private_objects_completed', 'et_core_page_resource_auto_clear' );
|
||||
add_action( 'as3cf_move_public_objects_cancelled', 'et_core_page_resource_auto_clear' );
|
||||
add_action( 'as3cf_move_public_objects_completed', 'et_core_page_resource_auto_clear' );
|
||||
add_action( 'as3cf_uploader_cancelled', 'et_core_page_resource_auto_clear' );
|
||||
add_action( 'as3cf_uploader_completed', 'et_core_page_resource_auto_clear' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is current request an et_fb_fetch_attachments AJAX call?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function doing_fetch_attachments() {
|
||||
if ( AS3CF_Utils::is_ajax() && ! empty( $_POST['action'] ) && 'et_fb_fetch_attachments' === $_POST['action'] ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn filtering on for WP_Query calls initiated by the et_fb_fetch_attachments AJAX call.
|
||||
*
|
||||
* @param \WP_Query $query
|
||||
*/
|
||||
public function pre_get_posts( \WP_Query $query ) {
|
||||
if ( ! empty( $query->query['post_type'] ) && 'attachment' === $query->query['post_type'] ) {
|
||||
$query->query_vars['suppress_filters'] = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handler for the 'the_posts' filter that runs local to provider URL filtering on Divi pages.
|
||||
*
|
||||
* @param array|\WP_Post $posts
|
||||
* @param \WP_Query $query
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function the_posts( $posts, $query ) {
|
||||
if (
|
||||
defined( 'ET_BUILDER_LAYOUT_POST_TYPE' ) &&
|
||||
! empty( $posts ) &&
|
||||
! empty( $query ) &&
|
||||
is_a( $query, 'WP_Query' ) &&
|
||||
! empty( $query->query_vars['post_type'] )
|
||||
) {
|
||||
if ( is_array( $posts ) ) {
|
||||
foreach ( $posts as $idx => $post ) {
|
||||
$posts[ $idx ] = $this->the_posts( $post, $query );
|
||||
}
|
||||
} elseif ( is_a( $posts, 'WP_Post' ) ) {
|
||||
$content_field = 'post_content';
|
||||
|
||||
if ( $this->doing_fetch_attachments() && 'attachment' === $posts->post_type ) {
|
||||
$content_field = 'guid';
|
||||
}
|
||||
|
||||
$posts->{$content_field} = apply_filters( 'as3cf_filter_post_local_to_provider', $posts->{$content_field} );
|
||||
}
|
||||
}
|
||||
|
||||
return $posts;
|
||||
}
|
||||
}
|
||||
233
classes/pro/integrations/easy-digital-downloads.php
Normal file
233
classes/pro/integrations/easy-digital-downloads.php
Normal file
@@ -0,0 +1,233 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations;
|
||||
|
||||
use Amazon_S3_And_CloudFront_Pro;
|
||||
use DeliciousBrains\WP_Offload_Media\Integrations\Integration;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Items\Update_Acl_Handler;
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
|
||||
class Easy_Digital_Downloads extends Integration {
|
||||
|
||||
/**
|
||||
* @var Amazon_S3_And_CloudFront_Pro
|
||||
*/
|
||||
protected $as3cf;
|
||||
|
||||
/**
|
||||
* Is installed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_installed(): bool {
|
||||
if ( class_exists( 'Easy_Digital_Downloads' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init integration.
|
||||
*/
|
||||
public function init() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setup() {
|
||||
// Set download method to redirect
|
||||
add_filter( 'edd_file_download_method', array( $this, 'set_download_method' ) );
|
||||
// Disable using symlinks for download.
|
||||
add_filter( 'edd_symlink_file_downloads', array( $this, 'disable_symlink_file_downloads' ) );
|
||||
// Hook into edd_requested_file to swap in the S3 secure URL
|
||||
add_filter( 'edd_requested_file', array( $this, 'get_download_url' ), 10, 3 );
|
||||
// Hook into the save download files metabox to apply the private ACL
|
||||
add_filter( 'edd_metabox_save_edd_download_files', array( $this, 'make_edd_files_private_on_provider' ), 11 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set download method
|
||||
*
|
||||
* @param string $method
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function set_download_method( $method ) {
|
||||
return 'redirect';
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable symlink file downloads
|
||||
*
|
||||
* @param bool $use_symlink
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function disable_symlink_file_downloads( $use_symlink ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the secure S3 url for downloads of a file
|
||||
*
|
||||
* @param string $file
|
||||
* @param array $download_files
|
||||
* @param string $file_key
|
||||
*
|
||||
* @return bool|string|WP_Error
|
||||
* @throws Exception
|
||||
*
|
||||
* @handles edd_requested_file
|
||||
*/
|
||||
public function get_download_url( $file, $download_files, $file_key ) {
|
||||
global $edd_options;
|
||||
|
||||
if ( empty( $file ) || empty( $download_files ) || empty( $file_key ) || ! is_array( $download_files ) || empty( $download_files[ $file_key ] ) ) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
$file_data = $download_files[ $file_key ];
|
||||
$file_name = $file_data['file'];
|
||||
$post_id = $file_data['attachment_id'];
|
||||
$expires = apply_filters( 'as3cf_edd_download_expires', 5 );
|
||||
$headers = apply_filters( 'as3cf_edd_download_headers', array(
|
||||
'ResponseContentDisposition' => 'attachment',
|
||||
), $file_data );
|
||||
|
||||
// Standard Offloaded Media Library Item.
|
||||
$as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
|
||||
|
||||
if ( $as3cf_item && ! is_wp_error( $as3cf_item ) ) {
|
||||
return $as3cf_item->get_provider_url( null, $expires, $headers );
|
||||
}
|
||||
|
||||
// Official EDD S3 addon upload - path should not start with '/', 'http', 'https' or 'ftp' or contain AWSAccessKeyId
|
||||
$url = parse_url( $file_name );
|
||||
|
||||
if ( ( '/' !== $file_name[0] && false === isset( $url['scheme'] ) ) || false !== ( strpos( $file_name, 'AWSAccessKeyId' ) ) ) {
|
||||
$bucket = ( isset( $edd_options['edd_amazon_s3_bucket'] ) ) ? trim( $edd_options['edd_amazon_s3_bucket'] ) : $this->as3cf->get_setting( 'bucket' );
|
||||
$expires = time() + $expires;
|
||||
|
||||
return $this->as3cf->get_provider_client()->get_object_url( $bucket, $file_name, $expires, $headers );
|
||||
}
|
||||
|
||||
// None S3 upload
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply ACL to files uploaded outside of EDD on save of EDD download files
|
||||
*
|
||||
* @param array $files
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function make_edd_files_private_on_provider( $files ) {
|
||||
global $post;
|
||||
|
||||
// get existing files attached to download
|
||||
$old_files = edd_get_download_files( $post->ID );
|
||||
$old_attachment_ids = wp_list_pluck( $old_files, 'attachment_id' );
|
||||
$new_attachment_ids = array();
|
||||
|
||||
/** @var Update_Acl_Handler $acl_handler */
|
||||
$acl_handler = $this->as3cf->get_item_handler( Update_Acl_Handler::get_item_handler_key_name() );
|
||||
|
||||
if ( is_array( $files ) ) {
|
||||
foreach ( $files as $file ) {
|
||||
$new_attachment_ids[] = $file['attachment_id'];
|
||||
|
||||
$as3cf_item = Media_Library_Item::get_by_source_id( $file['attachment_id'] );
|
||||
|
||||
if ( ! $as3cf_item ) {
|
||||
// not offloaded, ignore.
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $as3cf_item->is_private() ) {
|
||||
// already private
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $this->as3cf->is_pro_plugin_setup( true ) ) {
|
||||
|
||||
$options = array(
|
||||
'object_keys' => array( null ),
|
||||
'set_private' => true,
|
||||
);
|
||||
$result = $acl_handler->handle( $as3cf_item, $options );
|
||||
|
||||
if ( true === $result ) {
|
||||
$this->as3cf->make_acl_admin_notice( $as3cf_item );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// determine which attachments have been removed and maybe set to public
|
||||
$removed_attachment_ids = array_diff( $old_attachment_ids, $new_attachment_ids );
|
||||
$this->maybe_make_removed_edd_files_public( $removed_attachment_ids, $post->ID );
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove public ACL from attachments removed from a download
|
||||
* as long as they are not attached to any other downloads
|
||||
*
|
||||
* @param array $attachment_ids
|
||||
* @param integer $download_id
|
||||
*/
|
||||
function maybe_make_removed_edd_files_public( $attachment_ids, $download_id ) {
|
||||
global $wpdb;
|
||||
|
||||
/** @var Update_Acl_Handler $acl_handler */
|
||||
$acl_handler = $this->as3cf->get_item_handler( Update_Acl_Handler::get_item_handler_key_name() );
|
||||
|
||||
foreach ( $attachment_ids as $id ) {
|
||||
$as3cf_item = Media_Library_Item::get_by_source_id( $id );
|
||||
|
||||
if ( ! $as3cf_item ) {
|
||||
// Not offloaded, ignore.
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! $as3cf_item->is_private() ) {
|
||||
// already public
|
||||
continue;
|
||||
}
|
||||
|
||||
$length = strlen( $id );
|
||||
|
||||
// check the attachment isn't used by other downloads
|
||||
$sql = "
|
||||
SELECT COUNT(*)
|
||||
FROM `{$wpdb->prefix}postmeta`
|
||||
WHERE `{$wpdb->prefix}postmeta`.`meta_key` = 'edd_download_files'
|
||||
AND `{$wpdb->prefix}postmeta`.`post_id` != {$download_id}
|
||||
AND `{$wpdb->prefix}postmeta`.`meta_value` LIKE '%s:13:\"attachment_id\";s:{$length}:\"{$id}\"%'
|
||||
";
|
||||
|
||||
if ( $wpdb->get_var( $sql ) > 0 ) {
|
||||
// used for another download, ignore
|
||||
continue;
|
||||
}
|
||||
|
||||
// set acl to public
|
||||
$options = array(
|
||||
'object_keys' => array( null ),
|
||||
'set_private' => false,
|
||||
);
|
||||
$result = $acl_handler->handle( $as3cf_item, $options );
|
||||
|
||||
if ( true === $result ) {
|
||||
$this->as3cf->make_acl_admin_notice( $as3cf_item );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
242
classes/pro/integrations/elementor.php
Normal file
242
classes/pro/integrations/elementor.php
Normal file
@@ -0,0 +1,242 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Integrations\Integration;
|
||||
use Elementor\Core\Files\CSS\Post;
|
||||
use Elementor\Element_Base;
|
||||
use Elementor\Plugin;
|
||||
use AS3CF_Utils;
|
||||
|
||||
class Elementor extends Integration {
|
||||
/**
|
||||
* Keep track update_metadata recursion level
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $recursion_level = 0;
|
||||
|
||||
/**
|
||||
* Is Elementor installed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_installed(): bool {
|
||||
if ( defined( 'ELEMENTOR_VERSION' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init integration.
|
||||
*/
|
||||
public function init() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setup() {
|
||||
add_filter( 'elementor/editor/localize_settings', array( $this, 'localize_settings' ) );
|
||||
add_action( 'elementor/frontend/before_render', array( $this, 'frontend_before_render' ) );
|
||||
add_filter( 'update_post_metadata', array( $this, 'update_post_metadata' ), 10, 5 );
|
||||
add_action( 'wp_print_styles', array( $this, 'wp_print_styles' ) );
|
||||
|
||||
if ( isset( $_REQUEST['action'] ) && 'elementor_ajax' === $_REQUEST['action'] ) {
|
||||
add_filter( 'widget_update_callback', array( $this, 'widget_update_callback' ), 10, 2 );
|
||||
}
|
||||
|
||||
// Hooks to clear Elementor cache after OME bulk actions
|
||||
add_action( 'as3cf_copy_buckets_cancelled', array( $this, 'clear_elementor_css_cache' ) );
|
||||
add_action( 'as3cf_copy_buckets_completed', array( $this, 'clear_elementor_css_cache' ) );
|
||||
add_action( 'as3cf_download_and_remover_cancelled', array( $this, 'clear_elementor_css_cache' ) );
|
||||
add_action( 'as3cf_download_and_remover_completed', array( $this, 'clear_elementor_css_cache' ) );
|
||||
add_action( 'as3cf_move_objects_cancelled', array( $this, 'clear_elementor_css_cache' ) );
|
||||
add_action( 'as3cf_move_objects_completed', array( $this, 'clear_elementor_css_cache' ) );
|
||||
add_action( 'as3cf_move_private_objects_cancelled', array( $this, 'clear_elementor_css_cache' ) );
|
||||
add_action( 'as3cf_move_private_objects_completed', array( $this, 'clear_elementor_css_cache' ) );
|
||||
add_action( 'as3cf_move_public_objects_cancelled', array( $this, 'clear_elementor_css_cache' ) );
|
||||
add_action( 'as3cf_move_public_objects_completed', array( $this, 'clear_elementor_css_cache' ) );
|
||||
add_action( 'as3cf_uploader_cancelled', array( $this, 'clear_elementor_css_cache' ) );
|
||||
add_action( 'as3cf_uploader_completed', array( $this, 'clear_elementor_css_cache' ) );
|
||||
add_action( 'as3cf_elementor_analyze_and_repair_cancelled', array( $this, 'clear_elementor_css_cache' ) );
|
||||
add_action( 'as3cf_elementor_analyze_and_repair_completed', array( $this, 'clear_elementor_css_cache' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite media library URLs from local to remote when settings are read from
|
||||
* database.
|
||||
*
|
||||
* @param object $config
|
||||
*
|
||||
* @return object
|
||||
*
|
||||
* @handles elementor/editor/localize_settings
|
||||
*/
|
||||
public function localize_settings( $config ) {
|
||||
if ( ! is_array( $config ) || ! isset( $config['initial_document'] ) ) {
|
||||
return $config;
|
||||
}
|
||||
|
||||
if ( ! is_array( $config['initial_document'] ) || ! isset( $config['initial_document']['elements'] ) ) {
|
||||
return $config;
|
||||
}
|
||||
|
||||
$filtered = json_decode(
|
||||
$this->as3cf->filter_local->filter_post( json_encode( $config['initial_document']['elements'], JSON_UNESCAPED_SLASHES ) ),
|
||||
true
|
||||
);
|
||||
|
||||
// Avoid replacing content if the filtering failed
|
||||
if ( false !== $filtered ) {
|
||||
$config['initial_document']['elements'] = $filtered;
|
||||
}
|
||||
|
||||
return $config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace local URLs in settings that Elementor renders in HTML for some attributes, i.e json structs for
|
||||
* the section background slideshow
|
||||
*
|
||||
* @param Element_Base $element
|
||||
*
|
||||
* @handles elementor/frontend/before_render
|
||||
*/
|
||||
public function frontend_before_render( $element ) {
|
||||
$element->set_settings(
|
||||
json_decode(
|
||||
$this->as3cf->filter_local->filter_post( json_encode( $element->get_settings(), JSON_UNESCAPED_SLASHES ) ),
|
||||
true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle Elementor's call to update_metadata() for _elementor_data when saving
|
||||
* a post or page. Rewrites remote URLs to local.
|
||||
*
|
||||
* @param bool $check
|
||||
* @param int $object_id
|
||||
* @param string $meta_key
|
||||
* @param mixed $meta_value
|
||||
* @param mixed $prev_value
|
||||
*
|
||||
* @handles update_post_metadata
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function update_post_metadata( $check, $object_id, $meta_key, $meta_value, $prev_value ) {
|
||||
if ( '_elementor_css' === $meta_key ) {
|
||||
$this->rewrite_css( $object_id, $meta_value );
|
||||
|
||||
return $check;
|
||||
}
|
||||
|
||||
if ( '_elementor_data' !== $meta_key ) {
|
||||
return $check;
|
||||
}
|
||||
|
||||
// We expect the meta value to be a JSON formatted string from Elementor. Exit early if it's not.
|
||||
if ( ! is_string( $meta_value ) || ! AS3CF_Utils::is_json( $meta_value ) ) {
|
||||
return $check;
|
||||
}
|
||||
|
||||
// We're calling update_metadata recursively and need to make sure
|
||||
// we never nest deeper than one level.
|
||||
if ( 0 === $this->recursion_level ) {
|
||||
$this->recursion_level++;
|
||||
|
||||
// We get here from an update_metadata() call that has already done some string sanitizing
|
||||
// including wp_unslash(), but the original json from Elementor still needs slashes
|
||||
// removed for our filters to work.
|
||||
// Note: wp_unslash can't be used because it also unescapes any embedded HTML.
|
||||
$json = str_replace( '\/', '/', $meta_value );
|
||||
$json = $this->as3cf->filter_provider->filter_post( $json );
|
||||
$meta_value = wp_slash( str_replace( '/', '\/', $json ) );
|
||||
update_metadata( 'post', $object_id, '_elementor_data', $meta_value, $prev_value );
|
||||
|
||||
// Reset recursion level and let update_metadata we already handled saving the meta
|
||||
$this->recursion_level = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return $check;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites local URLs in generated CSS files
|
||||
*
|
||||
* @param int $object_id
|
||||
* @param array $meta_value
|
||||
*/
|
||||
private function rewrite_css( $object_id, $meta_value ) {
|
||||
if ( 'file' === $meta_value['status'] ) {
|
||||
$elementor_css = new Post( $object_id );
|
||||
$file = Post::get_base_uploads_dir() . Post::DEFAULT_FILES_DIR . $elementor_css->get_file_name();
|
||||
|
||||
if ( file_exists( $file ) ) {
|
||||
$old_content = file_get_contents( $file );
|
||||
if ( ! empty( $old_content ) ) {
|
||||
file_put_contents(
|
||||
$file,
|
||||
$this->as3cf->filter_local->filter_post( $old_content )
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Some widgets, specifically any standard WordPress widgets, make ajax requests back to the server
|
||||
* before the edited section gets rendered in the Elementor editor. When they do, Elementor picks up
|
||||
* properties directly from the saved post meta.
|
||||
*
|
||||
* @param array $instance
|
||||
* @param array $new_instance
|
||||
*
|
||||
* @handles widget_update_callback
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function widget_update_callback( $instance, $new_instance ) {
|
||||
return json_decode(
|
||||
$this->as3cf->filter_local->filter_post( json_encode( $instance, JSON_UNESCAPED_SLASHES ) ),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the Elementor cache
|
||||
*
|
||||
* @handles Multiple as3cf_*_completed and as3cf_*_cancelled actions
|
||||
*/
|
||||
public function clear_elementor_css_cache() {
|
||||
if ( class_exists( '\Elementor\Plugin' ) ) {
|
||||
Plugin::instance()->files_manager->clear_cache();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite URLs in Elementor frontend inline CSS before it's rendered/printed by WordPress
|
||||
*
|
||||
* @implements wp_print_styles
|
||||
*/
|
||||
public function wp_print_styles() {
|
||||
$wp_styles = wp_styles();
|
||||
if ( empty( $wp_styles->registered['elementor-frontend']->extra['after'] ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ( $wp_styles->registered['elementor-frontend']->extra['after'] as &$extra_css ) {
|
||||
$filtered_css = $this->as3cf->filter_local->filter_post( $extra_css );
|
||||
if ( ! empty( $filtered_css ) ) {
|
||||
$extra_css = $filtered_css;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
208
classes/pro/integrations/enable-media-replace.php
Normal file
208
classes/pro/integrations/enable-media-replace.php
Normal file
@@ -0,0 +1,208 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Integrations\Integration;
|
||||
use DeliciousBrains\WP_Offload_Media\Integrations\Media_Library;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Items\Remove_Provider_Handler;
|
||||
use Exception;
|
||||
|
||||
class Enable_Media_Replace extends Integration {
|
||||
/**
|
||||
* @var Media_Library
|
||||
*/
|
||||
private $media_library;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $wait_for_generate_attachment_metadata = false;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $downloaded_original;
|
||||
|
||||
/**
|
||||
* Is installed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_installed(): bool {
|
||||
if ( class_exists( 'EnableMediaReplace\EnableMediaReplacePlugin' ) || function_exists( 'enable_media_replace_init' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init integration.
|
||||
*/
|
||||
public function init() {
|
||||
$this->media_library = $this->as3cf->get_integration_manager()->get_integration( 'mlib' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setup() {
|
||||
// Make sure EMR allows OME to filter get_attached_file.
|
||||
add_filter( 'emr_unfiltered_get_attached_file', '__return_false' );
|
||||
|
||||
// Download the files and return their path so EMR doesn't get tripped up.
|
||||
add_filter( 'as3cf_get_attached_file', array( $this, 'download_file' ), 10, 4 );
|
||||
|
||||
// Although EMR uses wp_unique_filename, it discards that potentially new filename for plain replace, but does then use the following filter.
|
||||
add_filter( 'emr_unique_filename', array( $this, 'ensure_unique_filename' ), 10, 3 );
|
||||
|
||||
// Remove objects before offload happens, but don't re-offload just yet.
|
||||
add_filter( 'as3cf_update_attached_file', array( $this, 'remove_existing_provider_files_during_replace' ), 10, 2 );
|
||||
|
||||
if ( $this->is_replacing_media() ) {
|
||||
$this->wait_for_generate_attachment_metadata = true;
|
||||
|
||||
// Let the media library integration know it should wait for all attachment metadata.
|
||||
add_filter( 'as3cf_wait_for_generate_attachment_metadata', array( $this, 'wait_for_generate_attachment_metadata' ) );
|
||||
|
||||
// Wait for WordPress core to tell us it has finished generating thumbnails.
|
||||
add_filter( 'wp_generate_attachment_metadata', array( $this, 'generate_attachment_metadata_done' ) );
|
||||
|
||||
// Add our potentially downloaded primary file to the list files to remove.
|
||||
add_filter( 'as3cf_remove_local_files', array( $this, 'filter_remove_local_files' ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we waiting for the wp_generate_attachment_metadata filter to fire?
|
||||
*
|
||||
* @handles as3cf_wait_for_generate_attachment_metadata
|
||||
*
|
||||
* @param bool $wait
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function wait_for_generate_attachment_metadata( $wait ) {
|
||||
if ( $this->wait_for_generate_attachment_metadata ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $wait;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update internal state for waiting for attachment_metadata.
|
||||
*
|
||||
* @handles wp_generate_attachment_metadata
|
||||
*
|
||||
* @param array $metadata
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function generate_attachment_metadata_done( $metadata ) {
|
||||
$this->wait_for_generate_attachment_metadata = false;
|
||||
|
||||
return $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* If we've downloaded an existing primary file from the provider we add it to the
|
||||
* files_to_remove array when the Remove_Local handler runs.
|
||||
*
|
||||
* @handles as3cf_remove_local_files
|
||||
*
|
||||
* @param array $files_to_remove
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function filter_remove_local_files( $files_to_remove ) {
|
||||
if ( ! empty( $this->downloaded_original ) && file_exists( $this->downloaded_original ) && ! in_array( $this->downloaded_original, $files_to_remove ) ) {
|
||||
$files_to_remove[] = $this->downloaded_original;
|
||||
}
|
||||
|
||||
return $files_to_remove;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow the Enable Media Replace plugin to copy the provider file back to the local
|
||||
* server when the file is missing on the server via get_attached_file().
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $file
|
||||
* @param int $attachment_id
|
||||
* @param Media_Library_Item $as3cf_item
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function download_file( $url, $file, $attachment_id, Media_Library_Item $as3cf_item ) {
|
||||
$this->downloaded_original = $this->as3cf->plugin_compat->copy_image_to_server_on_action( 'media_replace_upload', false, $url, $file, $as3cf_item );
|
||||
|
||||
return $this->downloaded_original;
|
||||
}
|
||||
|
||||
/**
|
||||
* EMR deletes the original files before replace, then updates metadata etc.
|
||||
* So we should remove associated offloaded files too, and let normal (re)offload happen afterwards.
|
||||
*
|
||||
* @param string $file
|
||||
* @param int $attachment_id
|
||||
*
|
||||
* @return string
|
||||
* @throws Exception
|
||||
*/
|
||||
public function remove_existing_provider_files_during_replace( $file, $attachment_id ) {
|
||||
if ( ! $this->is_replacing_media() ) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
if ( ! $this->as3cf->is_plugin_setup( true ) ) {
|
||||
return $file;
|
||||
}
|
||||
|
||||
$as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
|
||||
|
||||
if ( ! empty( $as3cf_item ) ) {
|
||||
$remove_provider = $this->as3cf->get_item_handler( Remove_Provider_Handler::get_item_handler_key_name() );
|
||||
$remove_provider->handle( $as3cf_item, array( 'verify_exists_on_local' => false ) );
|
||||
|
||||
// By deleting the item here, a new one will be created by when EMR generates the thumbnails and our ML integration
|
||||
// picks it up. Ensuring that the object versioning string and object list are generated fresh.
|
||||
$as3cf_item->delete();
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we doing a media replacement?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_replacing_media() {
|
||||
$action = filter_input( INPUT_GET, 'action' );
|
||||
|
||||
if ( empty( $action ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return ( 'media_replace_upload' === sanitize_key( $action ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensure the generated filename for an image replaced with a new image is unique.
|
||||
*
|
||||
* @param string $filename File name that should be unique.
|
||||
* @param string $path Absolute path to where the file will go.
|
||||
* @param int $id Attachment ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function ensure_unique_filename( $filename, $path, $id ) {
|
||||
// Get extension.
|
||||
$ext = pathinfo( $filename, PATHINFO_EXTENSION );
|
||||
$ext = $ext ? ".$ext" : '';
|
||||
|
||||
return $this->media_library->filter_unique_filename( $filename, $ext, $path, $id );
|
||||
}
|
||||
}
|
||||
1531
classes/pro/integrations/media-library-pro.php
Normal file
1531
classes/pro/integrations/media-library-pro.php
Normal file
File diff suppressed because it is too large
Load Diff
375
classes/pro/integrations/meta-slider.php
Normal file
375
classes/pro/integrations/meta-slider.php
Normal file
@@ -0,0 +1,375 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations;
|
||||
|
||||
use AS3CF_Utils;
|
||||
use DeliciousBrains\WP_Offload_Media\Integrations\Integration;
|
||||
use DeliciousBrains\WP_Offload_Media\Integrations\Media_Library;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Download_Handler;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Upload_Handler;
|
||||
use Exception;
|
||||
|
||||
class Meta_Slider extends Integration {
|
||||
/**
|
||||
* @var Media_Library
|
||||
*/
|
||||
private $media_library;
|
||||
|
||||
/**
|
||||
* Keep track of get_metadata recursion level
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $get_postmeta_recursion_level = 0;
|
||||
|
||||
/**
|
||||
* Is installed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_installed(): bool {
|
||||
if ( class_exists( 'MetaSliderPlugin' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init integration.
|
||||
*/
|
||||
public function init() {
|
||||
$this->media_library = $this->as3cf->get_integration_manager()->get_integration( 'mlib' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setup() {
|
||||
add_filter( 'metaslider_attachment_url', array( $this, 'metaslider_attachment_url' ), 10, 2 );
|
||||
add_filter( 'sanitize_post_meta_amazonS3_info', array( $this, 'layer_slide_sanitize_post_meta' ), 10, 3 );
|
||||
add_filter( 'as3cf_pre_update_attachment_metadata', array( $this, 'layer_slide_abort_upload' ), 10, 4 );
|
||||
add_filter( 'as3cf_remove_attachment_paths', array( $this, 'layer_slide_remove_attachment_paths' ), 10, 4 );
|
||||
add_action( 'add_post_meta', array( $this, 'add_post_meta' ), 10, 3 );
|
||||
add_action( 'update_post_meta', array( $this, 'update_post_meta' ), 10, 4 );
|
||||
|
||||
// Maybe download primary image.
|
||||
add_filter( 'as3cf_get_attached_file', array( $this, 'download_for_resize' ), 10, 4 );
|
||||
|
||||
// Filter HTML in layer sliders when they are saved and fetched.
|
||||
add_filter( 'sanitize_post_meta_ml-slider_html', array( $this, 'sanitize_layer_slider_html' ) );
|
||||
add_filter( 'get_post_metadata', array( $this, 'filter_get_post_metadata' ), 10, 4 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Use the Provider URL for a Meta Slider slide image.
|
||||
*
|
||||
* @handles metaslider_attachment_url
|
||||
*
|
||||
* @param string $url
|
||||
* @param int $slide_id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function metaslider_attachment_url( $url, $slide_id ) {
|
||||
$provider_url = $this->media_library->wp_get_attachment_url( $url, $slide_id );
|
||||
|
||||
if ( ! is_wp_error( $provider_url ) && false !== $provider_url ) {
|
||||
return $provider_url;
|
||||
}
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Layer slide sanitize post meta.
|
||||
*
|
||||
* This fixes issues with 'Layer Slides', which uses `get_post_custom` to retrieve
|
||||
* attachment meta, but does not unserialize the data. This results in the `amazonS3_info`
|
||||
* key being double serialized when inserted into the database.
|
||||
*
|
||||
* @handles sanitize_post_meta_amazonS3_info
|
||||
*
|
||||
* @param mixed $meta_value
|
||||
* @param string $meta_key
|
||||
* @param string $object_type
|
||||
*
|
||||
* @return mixed
|
||||
*
|
||||
* Note: Legacy filter, kept for migration purposes.
|
||||
*/
|
||||
public function layer_slide_sanitize_post_meta( $meta_value, $meta_key, $object_type ) {
|
||||
if ( ! $this->is_layer_slide() ) {
|
||||
return $meta_value;
|
||||
}
|
||||
|
||||
return AS3CF_Utils::maybe_unserialize( $meta_value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Layer slide abort upload.
|
||||
*
|
||||
* 'Layer Slide' duplicates an attachment in the Media Library, but uses the same
|
||||
* file as the original. This prevents us trying to upload a new version to the bucket.
|
||||
*
|
||||
* @handles as3cf_pre_update_attachment_metadata
|
||||
*
|
||||
* @param bool $pre
|
||||
* @param mixed $data
|
||||
* @param int $post_id
|
||||
* @param Media_Library_Item|null $as3cf_item
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function layer_slide_abort_upload( $pre, $data, $post_id, Media_Library_Item $as3cf_item = null ) {
|
||||
if ( $this->is_layer_slide() && empty( $as3cf_item ) ) {
|
||||
$original_id = filter_input( INPUT_POST, 'slide_id' );
|
||||
if ( empty( $original_id ) ) {
|
||||
return $pre;
|
||||
}
|
||||
|
||||
$original_item = Media_Library_Item::get_by_source_id( $original_id );
|
||||
if ( empty( $original_item ) ) {
|
||||
return $pre;
|
||||
}
|
||||
|
||||
$as3cf_item = Media_Library_Item::create_from_source_id( $post_id );
|
||||
if ( empty( $as3cf_item ) ) {
|
||||
return $pre;
|
||||
}
|
||||
|
||||
$as3cf_item->set_path( $original_item->path() );
|
||||
$as3cf_item->set_original_path( $original_item->original_path() );
|
||||
$as3cf_item->set_extra_info( $original_item->extra_info() );
|
||||
$as3cf_item->save();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return $pre;
|
||||
}
|
||||
|
||||
/**
|
||||
* Layer slide remove attachment paths.
|
||||
*
|
||||
* Because 'Layer Slide' duplicates an attachment in the Media Library, but uses the same
|
||||
* file as the original we don't want to remove them from the bucket. Only the backup sizes
|
||||
* should be removed.
|
||||
*
|
||||
* @handles as3cf_remove_attachment_paths
|
||||
*
|
||||
* @param array $paths
|
||||
* @param int $post_id
|
||||
* @param Media_Library_Item $item
|
||||
* @param bool $remove_backup_sizes
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function layer_slide_remove_attachment_paths( $paths, $post_id, Media_Library_Item $item, $remove_backup_sizes ) {
|
||||
$slider = get_post_meta( $post_id, 'ml-slider_type', true );
|
||||
|
||||
if ( 'html_overlay' !== $slider ) {
|
||||
// Not a layer slide, return.
|
||||
return $paths;
|
||||
}
|
||||
|
||||
$meta = get_post_meta( $post_id, '_wp_attachment_metadata', true );
|
||||
|
||||
unset( $paths[ Media_Library_Item::primary_object_key() ] );
|
||||
if ( isset( $meta['sizes'] ) ) {
|
||||
foreach ( $meta['sizes'] as $size => $details ) {
|
||||
unset( $paths[ $size ] );
|
||||
}
|
||||
}
|
||||
|
||||
return $paths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is layer slide.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function is_layer_slide() {
|
||||
if ( 'create_html_overlay_slide' === filter_input( INPUT_POST, 'action' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add post meta
|
||||
*
|
||||
* @handles add_post_meta
|
||||
*
|
||||
* @param int $object_id
|
||||
* @param string $meta_key
|
||||
* @param mixed $_meta_value
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function add_post_meta( $object_id, $meta_key, $_meta_value ) {
|
||||
$this->maybe_upload_attachment_backup_sizes( $object_id, $meta_key, $_meta_value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update post meta
|
||||
*
|
||||
* @handles update_post_meta
|
||||
*
|
||||
* @param int $meta_id
|
||||
* @param int $object_id
|
||||
* @param string $meta_key
|
||||
* @param mixed $_meta_value
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
public function update_post_meta( $meta_id, $object_id, $meta_key, $_meta_value ) {
|
||||
$this->maybe_upload_attachment_backup_sizes( $object_id, $meta_key, $_meta_value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites remote URLs to local when Meta Slider saves HTML layer slides.
|
||||
*
|
||||
* @handles sanitize_post_meta_ml-slider_html
|
||||
*
|
||||
* @param string $meta_value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function sanitize_layer_slider_html( $meta_value ) {
|
||||
return $this->as3cf->filter_provider->filter_post( $meta_value );
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrites remote URLs to local when Meta Slider gets HTML layer slides.
|
||||
*
|
||||
* @handles get_post_metadata
|
||||
*
|
||||
* @param mixed $check
|
||||
* @param int $object_id
|
||||
* @param string $meta_key
|
||||
* @param mixed $meta_value
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function filter_get_post_metadata( $check, $object_id, $meta_key, $meta_value ) {
|
||||
// Exit early if this is not our key to process.
|
||||
if ( 'ml-slider_html' !== $meta_key ) {
|
||||
return $check;
|
||||
}
|
||||
|
||||
// We're calling get_metadata recursively and need to make sure
|
||||
// we never nest deeper than one level.
|
||||
if ( 0 === $this->get_postmeta_recursion_level ) {
|
||||
$this->get_postmeta_recursion_level++;
|
||||
$new_meta_value = get_metadata( 'post', $object_id, $meta_key, true );
|
||||
$new_meta_value = $this->as3cf->filter_local->filter_post( $new_meta_value );
|
||||
|
||||
// Reset recursion.
|
||||
$this->get_postmeta_recursion_level = 0;
|
||||
|
||||
return $new_meta_value;
|
||||
}
|
||||
|
||||
return $check;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allow meta slider to resize images that have been removed from local
|
||||
*
|
||||
* @handles as3cf_get_attached_file
|
||||
*
|
||||
* @param string $url
|
||||
* @param string $file
|
||||
* @param int $attachment_id
|
||||
* @param Media_Library_Item $as3cf_item
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function download_for_resize( $url, $file, $attachment_id, Media_Library_Item $as3cf_item ) {
|
||||
$action = filter_input( INPUT_POST, 'action' );
|
||||
if ( ! in_array( $action, array( 'resize_image_slide', 'create_html_overlay_slide' ) ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
$download_handler = $this->as3cf->get_item_handler( Download_Handler::get_item_handler_key_name() );
|
||||
$result = $download_handler->handle( $as3cf_item, array( 'full_source_paths' => array( $file ) ) );
|
||||
|
||||
if ( empty( $result ) || is_wp_error( $result ) ) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe upload attachment backup sizes
|
||||
*
|
||||
* @param int $object_id
|
||||
* @param string $meta_key
|
||||
* @param mixed $data
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function maybe_upload_attachment_backup_sizes( $object_id, $meta_key, $data ) {
|
||||
if ( '_wp_attachment_backup_sizes' !== $meta_key ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( 'resize_image_slide' !== filter_input( INPUT_POST, 'action' ) && ! $this->is_layer_slide() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->as3cf->is_plugin_setup( true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$item = Media_Library_Item::get_by_source_id( $object_id );
|
||||
|
||||
if ( ! $item && ! $this->as3cf->get_setting( 'copy-to-s3' ) ) {
|
||||
// Abort if not already offloaded to provider and the copy setting is off.
|
||||
return;
|
||||
}
|
||||
|
||||
$this->upload_attachment_backup_sizes( $object_id, $item, $data );
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload attachment backup sizes
|
||||
*
|
||||
* @param int $object_id
|
||||
* @param Media_Library_Item $as3cf_item
|
||||
* @param mixed $data
|
||||
*
|
||||
* @throws Exception
|
||||
*/
|
||||
private function upload_attachment_backup_sizes( $object_id, Media_Library_Item $as3cf_item, $data ) {
|
||||
foreach ( $data as $key => $file ) {
|
||||
if ( ! isset( $file['path'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$objects = $as3cf_item->objects();
|
||||
|
||||
if ( ! empty( $objects[ $key ] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$options = array(
|
||||
'offloaded_files' => $as3cf_item->offloaded_files(),
|
||||
);
|
||||
|
||||
$objects[ $key ] = array(
|
||||
'source_file' => wp_basename( $file['path'] ),
|
||||
'is_private' => false,
|
||||
);
|
||||
|
||||
$as3cf_item->set_objects( $objects );
|
||||
$upload_handler = $this->as3cf->get_item_handler( Upload_Handler::get_item_handler_key_name() );
|
||||
$upload_handler->handle( $as3cf_item, $options );
|
||||
}
|
||||
}
|
||||
}
|
||||
532
classes/pro/integrations/woocommerce.php
Normal file
532
classes/pro/integrations/woocommerce.php
Normal file
@@ -0,0 +1,532 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations;
|
||||
|
||||
use Amazon_S3_And_CloudFront_Pro;
|
||||
use AS3CF_Utils;
|
||||
use DeliciousBrains\WP_Offload_Media\Integrations\Integration;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Items\Update_Acl_Handler;
|
||||
use Exception;
|
||||
use WC_Product;
|
||||
use WC_Product_Download;
|
||||
|
||||
class Woocommerce extends Integration {
|
||||
|
||||
/**
|
||||
* Keep track of URLs that we already transformed to remote URLs
|
||||
* when product object was re-hydrated
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $re_hydrated_urls = array();
|
||||
|
||||
/**
|
||||
* @var Amazon_S3_And_CloudFront_Pro
|
||||
*/
|
||||
protected $as3cf;
|
||||
|
||||
/**
|
||||
* Is installed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_installed(): bool {
|
||||
if ( class_exists( 'WooCommerce' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init integration.
|
||||
*/
|
||||
public function init() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setup() {
|
||||
add_action( 'admin_enqueue_scripts', array( $this, 'admin_scripts' ) );
|
||||
add_action( 'woocommerce_process_product_file_download_paths', array( $this, 'make_files_private_on_provider' ), 10, 3 );
|
||||
add_filter( 'woocommerce_file_download_path', array( $this, 'woocommerce_file_download_path' ), 20, 1 );
|
||||
add_action( 'woocommerce_admin_process_product_object', array( $this, 'woocommerce_admin_process_product_object' ), 10, 1 );
|
||||
add_action( 'woocommerce_admin_process_variation_object', array( $this, 'woocommerce_admin_process_product_object' ), 10, 1 );
|
||||
add_action( 'woocommerce_download_file_as3cf', array( $this, 'download_file' ), 10, 2 );
|
||||
add_filter( 'woocommerce_file_download_method', array( $this, 'add_download_method' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function admin_scripts() {
|
||||
$screen = get_current_screen();
|
||||
|
||||
if ( in_array( $screen->id, array( 'product', 'edit-product' ) ) ) {
|
||||
if ( ! $this->as3cf->is_pro_plugin_setup( true ) ) {
|
||||
// Don't allow new shortcodes if Pro not set up
|
||||
return;
|
||||
}
|
||||
|
||||
wp_enqueue_media();
|
||||
$this->as3cf->enqueue_script( 'as3cf-woo-script', 'assets/js/pro/integrations/woocommerce', array(
|
||||
'jquery',
|
||||
'wp-util',
|
||||
) );
|
||||
|
||||
wp_localize_script( 'as3cf-woo-script', 'as3cf_woo', array(
|
||||
'strings' => array(
|
||||
'media_modal_title' => __( 'Select Downloadable File', 'as3cf-woocommerce' ),
|
||||
'media_modal_button' => __( 'Insert File', 'as3cf-woocommerce' ),
|
||||
'input_placeholder' => __( 'Retrieving...', 'as3cf-woocommerce' ),
|
||||
),
|
||||
'nonces' => array(
|
||||
'is_amazon_provider_attachment' => wp_create_nonce( 'as3cf_woo_is_amazon_provider_attachment' ),
|
||||
),
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Make file private on provider.
|
||||
*
|
||||
* @param int $post_id
|
||||
* @param int $variation_id
|
||||
* @param array $files
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function make_files_private_on_provider( $post_id, $variation_id, $files ) {
|
||||
$new_attachments = array();
|
||||
$post_id = $variation_id > 0 ? $variation_id : $post_id;
|
||||
|
||||
/** @var Update_Acl_Handler $acl_handler */
|
||||
$acl_handler = $this->as3cf->get_item_handler( Update_Acl_Handler::get_item_handler_key_name() );
|
||||
|
||||
foreach ( $files as $file ) {
|
||||
$url = $this->downloadable_file_url( $file );
|
||||
$item_source = ! empty( $url ) ? $this->as3cf->filter_local->get_item_source_from_url( $url ) : false;
|
||||
|
||||
if ( false !== $item_source && ! Media_Library_Item::is_empty_item_source( $item_source ) ) {
|
||||
$attachment_id = $item_source['id'];
|
||||
} else {
|
||||
// Attachment id could not be determined, ignore
|
||||
continue;
|
||||
}
|
||||
|
||||
$size = $this->as3cf->filter_local->get_size_string_from_url( $item_source, $url );
|
||||
$new_attachments[] = $attachment_id . '-' . $size;
|
||||
|
||||
$as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
|
||||
|
||||
if ( ! $as3cf_item ) {
|
||||
// Not offloaded, ignore.
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $as3cf_item->is_private( $size ) ) {
|
||||
// Item is already private, carry on
|
||||
continue;
|
||||
}
|
||||
|
||||
// Only set new files as private if the Pro plugin is setup
|
||||
if ( $this->as3cf->is_pro_plugin_setup( true ) ) {
|
||||
$options = array(
|
||||
'object_keys' => array( $size ),
|
||||
'set_private' => true,
|
||||
);
|
||||
$result = $acl_handler->handle( $as3cf_item, $options );
|
||||
if ( true === $result ) {
|
||||
$this->as3cf->make_acl_admin_notice( $as3cf_item, $size );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->maybe_make_removed_files_public( $post_id, $new_attachments );
|
||||
|
||||
return $files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe rewrite WooCommerce product file value to provider URL.
|
||||
*
|
||||
* @handles woocommerce_file_download_path
|
||||
*
|
||||
* @param string $file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function woocommerce_file_download_path( $file ) {
|
||||
$size = null;
|
||||
$remote_url = false;
|
||||
$attachment_id = 0;
|
||||
|
||||
// Is it a local URL ?
|
||||
$item_source = $this->as3cf->filter_local->get_item_source_from_url( $file );
|
||||
if ( false !== $item_source && ! Media_Library_Item::is_empty_item_source( $item_source ) ) {
|
||||
$attachment_id = $item_source['id'];
|
||||
}
|
||||
|
||||
if ( $attachment_id > 0 ) {
|
||||
$size = $this->as3cf->filter_local->get_size_string_from_url( $item_source, $file );
|
||||
$as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
|
||||
if ( ! empty( $as3cf_item ) ) {
|
||||
$remote_url = $as3cf_item->get_provider_url( $size );
|
||||
}
|
||||
}
|
||||
|
||||
// Is it our shortcode ?
|
||||
$atts = $this->get_shortcode_atts( $file );
|
||||
if ( isset( $atts['id'] ) ) {
|
||||
$attachment_id = (int) $this->get_attachment_id_from_shortcode( $file );
|
||||
if ( $attachment_id > 0 ) {
|
||||
$as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
|
||||
if ( ! empty( $as3cf_item ) ) {
|
||||
$remote_url = $as3cf_item->get_provider_url();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( is_string( $remote_url ) && ! empty( $remote_url ) ) {
|
||||
$this->re_hydrated_urls[ $remote_url ] = array(
|
||||
'id' => $attachment_id,
|
||||
'size' => $size,
|
||||
);
|
||||
|
||||
return $remote_url;
|
||||
}
|
||||
|
||||
return $file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe rewrite WooCommerce product file URLs to local URLs.
|
||||
*
|
||||
* @handles woocommerce_admin_process_product_object
|
||||
* @handles woocommerce_admin_process_variation_object
|
||||
*
|
||||
* @param WC_Product $product
|
||||
*/
|
||||
public function woocommerce_admin_process_product_object( $product ) {
|
||||
$downloads = $product->get_downloads();
|
||||
foreach ( $downloads as $download ) {
|
||||
$url = $this->downloadable_file_url( $download );
|
||||
|
||||
// Is this a shortcode ?
|
||||
$attachment_id = (int) $this->get_attachment_id_from_shortcode( $url );
|
||||
|
||||
// If not, is it a remote URL?
|
||||
if ( ! $attachment_id ) {
|
||||
$item_source = $this->as3cf->filter_provider->get_item_source_from_url( $url );
|
||||
if ( false !== $item_source && ! Media_Library_Item::is_empty_item_source( $item_source ) ) {
|
||||
$attachment_id = $item_source['id'];
|
||||
}
|
||||
}
|
||||
|
||||
if ( $attachment_id > 0 ) {
|
||||
$as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
|
||||
if ( false !== $as3cf_item ) {
|
||||
$size = $this->as3cf->filter_local->get_size_string_from_url( $as3cf_item->get_item_source_array(), $url );
|
||||
$url = $as3cf_item->get_local_url( $size );
|
||||
$download->set_file( $url );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attachment id from shortcode.
|
||||
*
|
||||
* @param string $shortcode
|
||||
*
|
||||
* @return int|bool
|
||||
*/
|
||||
public function get_attachment_id_from_shortcode( $shortcode ) {
|
||||
$atts = $this->get_shortcode_atts( $shortcode );
|
||||
|
||||
if ( isset( $atts['id'] ) ) {
|
||||
return intval( $atts['id'] );
|
||||
}
|
||||
|
||||
if ( ! isset( $atts['bucket'] ) || ! isset( $atts['object'] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Media_Library_Item::get_source_id_by_bucket_and_path( $atts['bucket'], $atts['object'] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get shortcode atts.
|
||||
*
|
||||
* @param string $shortcode
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_shortcode_atts( $shortcode ) {
|
||||
$shortcode = trim( stripcslashes( $shortcode ) );
|
||||
$shortcode = ltrim( $shortcode, '[' );
|
||||
$shortcode = rtrim( $shortcode, ']' );
|
||||
$shortcode = shortcode_parse_atts( $shortcode );
|
||||
|
||||
return $shortcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove private ACL from provider if no longer used by WooCommerce.
|
||||
*
|
||||
* @param int $post_id
|
||||
* @param array $new_attachments List of attachments. Attachment id AND size on the format "$id-$size"
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function maybe_make_removed_files_public( $post_id, $new_attachments ) {
|
||||
$old_files = get_post_meta( $post_id, '_downloadable_files', true );
|
||||
$old_attachments = array();
|
||||
|
||||
/** @var Update_Acl_Handler $acl_handler */
|
||||
$acl_handler = $this->as3cf->get_item_handler( Update_Acl_Handler::get_item_handler_key_name() );
|
||||
|
||||
if ( is_array( $old_files ) ) {
|
||||
foreach ( $old_files as $old_file ) {
|
||||
$url = $this->downloadable_file_url( $old_file );
|
||||
$item_source = ! empty( $url ) ? $this->as3cf->filter_local->get_item_source_from_url( $url ) : false;
|
||||
|
||||
if ( false !== $item_source && ! Media_Library_Item::is_empty_item_source( $item_source ) ) {
|
||||
$size = $this->as3cf->filter_local->get_size_string_from_url( $item_source, $url );
|
||||
$old_attachments[] = $item_source['id'] . '-' . $size;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$removed_attachments = array_diff( $old_attachments, $new_attachments );
|
||||
|
||||
if ( empty( $removed_attachments ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
global $wpdb;
|
||||
|
||||
foreach ( $removed_attachments as $attachment ) {
|
||||
$parts = explode( '-', $attachment );
|
||||
$attachment_id = (int) $parts[0];
|
||||
$size = empty( $parts[1] ) ? null : $parts[1];
|
||||
$as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
|
||||
|
||||
if ( ! $as3cf_item ) {
|
||||
// Not offloaded, ignore.
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( ! $as3cf_item->is_private( $size ) ) {
|
||||
// Item is already public, carry on
|
||||
continue;
|
||||
}
|
||||
|
||||
$local_url = AS3CF_Utils::reduce_url( strval( $as3cf_item->get_local_url( $size ) ) );
|
||||
|
||||
$file = AS3CF_Utils::is_full_size( $size ) ? null : wp_basename( $as3cf_item->path( $size ) );
|
||||
$bucket = preg_quote( $as3cf_item->bucket(), '@' );
|
||||
$key = preg_quote( $as3cf_item->key( $file ), '@' );
|
||||
$url = preg_quote( $local_url, '@' );
|
||||
|
||||
// Check the attachment isn't used by other downloads
|
||||
$sql = $wpdb->prepare( "
|
||||
SELECT meta_value
|
||||
FROM $wpdb->postmeta
|
||||
WHERE post_id != %d
|
||||
AND meta_key = %s
|
||||
AND (meta_value LIKE %s OR meta_value like %s)
|
||||
", $post_id, '_downloadable_files', '%amazon_s3%', '%' . $local_url . '%' );
|
||||
|
||||
$results = $wpdb->get_results( $sql, ARRAY_A );
|
||||
|
||||
foreach ( $results as $result ) {
|
||||
// WP Offload Media
|
||||
if ( preg_match( '@\[amazon_s3\sid=[\'\"]*' . $attachment_id . '[\'\"]*\]@', $result['meta_value'] ) ) {
|
||||
continue 2;
|
||||
}
|
||||
|
||||
// Official WooCommerce S3 addon
|
||||
if ( preg_match( '@\[amazon_s3\sobject=[\'\"]*' . $key . '[\'\"]*\sbucket=[\'\"]*' . $bucket . '[\'\"]*\]@', $result['meta_value'] ) ) {
|
||||
continue 2;
|
||||
}
|
||||
if ( preg_match( '@\[amazon_s3\sbucket=[\'\"]*' . $bucket . '[\'\"]*\sobject=[\'\"]*' . $key . '[\'\"]*\]@', $result['meta_value'] ) ) {
|
||||
continue 2;
|
||||
}
|
||||
|
||||
if ( preg_match( '@' . $url . '@', $result['meta_value'] ) ) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Set ACL to public
|
||||
$options = array(
|
||||
'object_keys' => array( $size ),
|
||||
'set_private' => false,
|
||||
);
|
||||
$result = $acl_handler->handle( $as3cf_item, $options );
|
||||
|
||||
if ( true === $result ) {
|
||||
$this->as3cf->make_acl_admin_notice( $as3cf_item, $size );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add download method to WooCommerce.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function add_download_method() {
|
||||
return 'as3cf';
|
||||
}
|
||||
|
||||
/**
|
||||
* Use S3 secure link to download file.
|
||||
*
|
||||
* @param string $file_path
|
||||
* @param int $filename
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function download_file( $file_path, $filename ) {
|
||||
$size = null;
|
||||
$attachment_id = 0;
|
||||
|
||||
/*
|
||||
* Is this a remote URL that we already handled when the product object
|
||||
* was re-hydrated?
|
||||
*/
|
||||
if ( isset( $this->re_hydrated_urls[ $file_path ] ) ) {
|
||||
$attachment_id = $this->re_hydrated_urls[ $file_path ]['id'];
|
||||
$size = $this->re_hydrated_urls[ $file_path ]['size'];
|
||||
}
|
||||
|
||||
/*
|
||||
* Is this a shortcode that resolves to an attachment?
|
||||
*/
|
||||
if ( ! $attachment_id ) {
|
||||
$attachment_id = (int) $this->get_attachment_id_from_shortcode( $file_path );
|
||||
}
|
||||
|
||||
/*
|
||||
* If no attachment was found via shortcode, it's possible that
|
||||
* $file_path is a URL to the local version of an offloaded item
|
||||
*/
|
||||
if ( ! $attachment_id ) {
|
||||
$item_source = $this->as3cf->filter_local->get_item_source_from_url( $file_path );
|
||||
if ( false !== $item_source && ! Media_Library_Item::is_empty_item_source( $item_source ) ) {
|
||||
$attachment_id = $item_source['id'];
|
||||
$size = $this->as3cf->filter_local->get_size_string_from_url( $item_source, $file_path );
|
||||
}
|
||||
}
|
||||
|
||||
$expires = apply_filters( 'as3cf_woocommerce_download_expires', 5 );
|
||||
|
||||
$file_data = array(
|
||||
'name' => $filename,
|
||||
'file' => $file_path,
|
||||
);
|
||||
|
||||
if ( ! $attachment_id || ! Media_Library_Item::get_by_source_id( $attachment_id ) ) {
|
||||
/*
|
||||
This addon is meant to be a drop-in replacement for the
|
||||
WooCommerce Amazon S3 Storage extension. The latter doesn't encourage people
|
||||
to add the file to the Media Library, so even though we can't get an
|
||||
attachment ID for the shortcode, we should still serve the download
|
||||
if the shortcode contains the `bucket` and `object` attributes.
|
||||
*/
|
||||
$atts = $this->get_shortcode_atts( $file_path );
|
||||
|
||||
if ( isset( $atts['bucket'] ) && isset( $atts['object'] ) ) {
|
||||
$bucket_setting = $this->as3cf->get_setting( 'bucket' );
|
||||
|
||||
if ( $bucket_setting === $atts['bucket'] ) {
|
||||
$region = $this->as3cf->get_setting( 'region' );
|
||||
} else {
|
||||
$region = $this->as3cf->get_bucket_region( $atts['bucket'] );
|
||||
}
|
||||
|
||||
if ( is_wp_error( $region ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
$expires = time() + $expires;
|
||||
$headers = apply_filters( 'as3cf_woocommerce_download_headers', array( 'ResponseContentDisposition' => 'attachment' ), $file_data );
|
||||
$secure_url = $this->as3cf->get_provider_client( $region, true )->get_object_url( $atts['bucket'], $atts['object'], $expires, $headers );
|
||||
} catch ( Exception $e ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_filter( 'wp_die_handler', array( $this, 'set_wp_die_handler' ) );
|
||||
header( 'Location: ' . $secure_url );
|
||||
wp_die();
|
||||
}
|
||||
|
||||
// Handle shortcode inputs where the file has been removed from S3
|
||||
// Parse the url, shortcodes do not return a host
|
||||
$url = parse_url( $file_path );
|
||||
|
||||
if ( ! isset( $url['host'] ) && ! empty( $attachment_id ) ) {
|
||||
$file_path = wp_get_attachment_url( $attachment_id );
|
||||
$filename = wp_basename( $file_path );
|
||||
}
|
||||
|
||||
// File not on S3, trigger WooCommerce saved download method
|
||||
$method = get_option( 'woocommerce_file_download_method', 'force' );
|
||||
do_action( 'woocommerce_download_file_' . $method, $file_path, $filename );
|
||||
} else {
|
||||
$file_data['attachment_id'] = $attachment_id;
|
||||
$headers = apply_filters( 'as3cf_woocommerce_download_headers', array( 'ResponseContentDisposition' => 'attachment' ), $file_data );
|
||||
$as3cf_item = Media_Library_Item::get_by_source_id( $attachment_id );
|
||||
if ( ! empty( $as3cf_item ) ) {
|
||||
$secure_url = $as3cf_item->get_provider_url( $size, $expires, $headers );
|
||||
|
||||
add_filter( 'wp_die_handler', array( $this, 'set_wp_die_handler' ) );
|
||||
header( 'Location: ' . $secure_url );
|
||||
wp_die();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter handler for set_wp_die_handler.
|
||||
*
|
||||
* @param array $handler
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function set_wp_die_handler( $handler ) {
|
||||
return array( $this, 'woocommerce_die_handler' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Replacement for the default wp_die() handler
|
||||
*/
|
||||
public function woocommerce_die_handler() {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the downloadable file URL from WooCommerce object
|
||||
*
|
||||
* @param WC_Product_Download|array $file
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
private function downloadable_file_url( $file ) {
|
||||
if ( $file instanceof WC_Product_Download ) {
|
||||
return $file->get_file();
|
||||
} elseif ( is_array( $file ) && isset( $file['file'] ) ) {
|
||||
return $file['file'];
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
70
classes/pro/integrations/wpml.php
Normal file
70
classes/pro/integrations/wpml.php
Normal file
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Integrations\Integration;
|
||||
|
||||
class Wpml extends Integration {
|
||||
|
||||
/**
|
||||
* Is installed?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function is_installed(): bool {
|
||||
if ( class_exists( 'SitePress' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init integration.
|
||||
*/
|
||||
public function init() {
|
||||
// Nothing to do.
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function setup() {
|
||||
add_action( 'wpml_media_create_duplicate_attachment', array( $this, 'duplicate_offloaded_item' ), 10, 2 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Duplicate original item's offload data for new duplicate Media Library item.
|
||||
*
|
||||
* WPML duplicates postmeta in reverse order which unfortunately means we can't catch the new attachment and offload it.
|
||||
* But, WPML does fire an action after each item is duplicated, so we can just duplicate our data too.
|
||||
*
|
||||
* @param int $attachment_id
|
||||
* @param int $new_attachment_id
|
||||
*/
|
||||
public function duplicate_offloaded_item( $attachment_id, $new_attachment_id ) {
|
||||
$old_item = Media_Library_Item::get_by_source_id( $attachment_id );
|
||||
|
||||
if ( $old_item ) {
|
||||
$as3cf_item = Media_Library_Item::get_by_source_id( $new_attachment_id );
|
||||
|
||||
if ( ! $as3cf_item ) {
|
||||
$as3cf_item = new Media_Library_Item(
|
||||
$old_item->provider(),
|
||||
$old_item->region(),
|
||||
$old_item->bucket(),
|
||||
$old_item->path(),
|
||||
$old_item->is_private(),
|
||||
$new_attachment_id,
|
||||
$old_item->source_path(),
|
||||
wp_basename( $old_item->original_source_path() ),
|
||||
$old_item->extra_info()
|
||||
);
|
||||
|
||||
$as3cf_item->save();
|
||||
$as3cf_item->duplicate_filesize_total( $attachment_id );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
classes/pro/items/remove-provider-handler.php
Normal file
20
classes/pro/items/remove-provider-handler.php
Normal file
@@ -0,0 +1,20 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Items;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Remove_Provider_Handler as Remove_Provider_Handler_Lite;
|
||||
|
||||
class Remove_Provider_Handler extends Remove_Provider_Handler_Lite {
|
||||
/**
|
||||
* The default options that should be used if none supplied.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function default_options() {
|
||||
return array(
|
||||
'object_keys' => array(),
|
||||
'offloaded_files' => array(),
|
||||
'verify_exists_on_local' => true,
|
||||
);
|
||||
}
|
||||
}
|
||||
162
classes/pro/items/update-acl-handler.php
Normal file
162
classes/pro/items/update-acl-handler.php
Normal file
@@ -0,0 +1,162 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Items;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item_Handler;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Manifest;
|
||||
use Exception;
|
||||
use WP_Error;
|
||||
|
||||
class Update_Acl_Handler extends Item_Handler {
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected static $item_handler_key = 'update-acl';
|
||||
|
||||
/**
|
||||
* The default options that should be used if none supplied.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function default_options() {
|
||||
return array(
|
||||
'object_keys' => array( Item::primary_object_key() ),
|
||||
'set_private' => true,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param array $options
|
||||
*
|
||||
* @return Manifest
|
||||
*/
|
||||
protected function pre_handle( Item $as3cf_item, array $options ) {
|
||||
$manifest = new Manifest();
|
||||
|
||||
foreach ( $options['object_keys'] as $object_key ) {
|
||||
$manifest->objects[] = array(
|
||||
'object_key' => $object_key,
|
||||
'set_private' => $options['set_private'],
|
||||
);
|
||||
}
|
||||
|
||||
return $manifest;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the acl update
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param Manifest $manifest
|
||||
* @param array $options
|
||||
*
|
||||
* @return bool|WP_Error
|
||||
*/
|
||||
protected function handle_item( Item $as3cf_item, Manifest $manifest, array $options ) {
|
||||
foreach ( $manifest->objects as $object_to_update ) {
|
||||
if ( $as3cf_item->is_private( $object_to_update['object_key'] ) === $object_to_update['set_private'] ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$tried = false;
|
||||
$region = empty( $as3cf_item->region() ) ? false : $as3cf_item->region();
|
||||
$acl = $object_to_update['set_private'] ? $this->as3cf->get_storage_provider()->get_private_acl() : $this->as3cf->get_storage_provider()->get_default_acl();
|
||||
|
||||
$previous_state = clone $as3cf_item;
|
||||
$as3cf_item->set_is_private( $object_to_update['set_private'], $object_to_update['object_key'] );
|
||||
|
||||
// Only set ACL if allowed.
|
||||
if ( ! empty( $acl ) && $this->as3cf->use_acl_for_intermediate_size( 0, $object_to_update['object_key'], $as3cf_item->bucket(), $previous_state ) ) {
|
||||
$tried = true;
|
||||
|
||||
$args = array(
|
||||
'Bucket' => $as3cf_item->bucket(),
|
||||
'Key' => $previous_state->provider_key( $object_to_update['object_key'] ),
|
||||
'ACL' => $acl,
|
||||
);
|
||||
|
||||
try {
|
||||
$provider_client = $this->as3cf->get_provider_client( $region, true );
|
||||
$provider_client->update_object_acl( $args );
|
||||
$as3cf_item->save();
|
||||
} catch ( Exception $e ) {
|
||||
$error_msg = 'Error setting ACL to "' . $acl . '" for ' . $previous_state->provider_key( $object_to_update['object_key'] ) . ': ' . $e->getMessage();
|
||||
|
||||
return $this->return_handler_error( $error_msg );
|
||||
}
|
||||
}
|
||||
|
||||
// If signed urls enabled then may need to move object, which is a copy and delete.
|
||||
if ( $this->as3cf->private_prefix_enabled() ) {
|
||||
$tried = true;
|
||||
|
||||
$args = array(
|
||||
'Bucket' => $as3cf_item->bucket(),
|
||||
'Key' => $as3cf_item->provider_key( $object_to_update['object_key'] ),
|
||||
'CopySource' => urlencode( "{$as3cf_item->bucket()}/" . $previous_state->provider_key( $object_to_update['object_key'] ) ),
|
||||
);
|
||||
|
||||
$items[] = $args;
|
||||
|
||||
try {
|
||||
$provider_client = $this->as3cf->get_provider_client( $region, true );
|
||||
$failures = $provider_client->copy_objects( $items );
|
||||
|
||||
if ( empty( $failures ) ) {
|
||||
$provider_client->delete_object( array(
|
||||
'Bucket' => $as3cf_item->bucket(),
|
||||
'Key' => $previous_state->provider_key( $object_to_update['object_key'] ),
|
||||
) );
|
||||
} else {
|
||||
$failure = array_shift( $failures );
|
||||
|
||||
$error_msg = sprintf(
|
||||
__( 'Error moving %1$s to %2$s for %5$s %3$d: %4$s', 'amazon-s3-and-cloudfront' ),
|
||||
$previous_state->provider_key( $object_to_update['object_key'] ),
|
||||
$failure['Key'],
|
||||
$as3cf_item->source_id(),
|
||||
$failure['Message'],
|
||||
$this->as3cf->get_source_type_name( $as3cf_item->source_type() )
|
||||
);
|
||||
|
||||
return $this->return_handler_error( $error_msg );
|
||||
}
|
||||
$as3cf_item->save();
|
||||
} catch ( Exception $e ) {
|
||||
$error_msg = sprintf(
|
||||
__( 'Error updating access for %1$s: %2$s', 'amazon-s3-and-cloudfront' ),
|
||||
$previous_state->path( $object_to_update['object_key'] ),
|
||||
$e->getMessage()
|
||||
);
|
||||
|
||||
return $this->return_handler_error( $error_msg );
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! $tried ) {
|
||||
$error_msg = __( 'Error updating item access, neither ACL updating for bucket or Private Path handling enabled.', 'amazon-s3-and-cloudfront' );
|
||||
|
||||
return $this->return_handler_error( $error_msg );
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform post handle tasks.
|
||||
*
|
||||
* @param Item $as3cf_item
|
||||
* @param Manifest $manifest
|
||||
* @param array $options
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function post_handle( Item $as3cf_item, Manifest $manifest, array $options ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
298
classes/pro/providers/delivery/aws-cloudfront-pro.php
Normal file
298
classes/pro/providers/delivery/aws-cloudfront-pro.php
Normal file
@@ -0,0 +1,298 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Providers\Delivery;
|
||||
|
||||
use AS3CF_Utils;
|
||||
use DeliciousBrains\WP_Offload_Media\Aws3\Aws\CloudFront\UrlSigner;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
use DeliciousBrains\WP_Offload_Media\Providers\Delivery\AWS_CloudFront;
|
||||
use DeliciousBrains\WP_Offload_Media\Settings\Validator_Interface;
|
||||
use WP_Error as AS3CF_Result;
|
||||
|
||||
class AWS_CloudFront_Pro extends AWS_CloudFront {
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $signed_urls_key_id_constants = array(
|
||||
'AS3CF_AWS_CLOUDFRONT_SIGNED_URLS_KEY_ID',
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $signed_urls_key_file_path_constants = array(
|
||||
'AS3CF_AWS_CLOUDFRONT_SIGNED_URLS_KEY_FILE_PATH',
|
||||
);
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $signed_urls_object_prefix_constants = array(
|
||||
'AS3CF_AWS_CLOUDFRONT_SIGNED_URLS_OBJECT_PREFIX',
|
||||
);
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public static function signed_urls_support_desc() {
|
||||
return __( 'Private Media Supported', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Title used in various places for enabling Signed URLs.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function signed_urls_option_name() {
|
||||
return __( 'Serve Private Media from CloudFront', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the URL for the CloudFront Signed URLs doc.
|
||||
*
|
||||
* @param string $section Optional section to go to in page.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_signed_urls_setup_doc_url( string $section = '' ): string {
|
||||
global $as3cf;
|
||||
|
||||
return $as3cf::dbrains_url(
|
||||
'/wp-offload-media/doc/serve-private-media-signed-cloudfront-urls/',
|
||||
array( 'utm_campaign' => 'support+docs' ),
|
||||
$section
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Description used in various places for enabling Signed URLs.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function signed_urls_option_description(): string {
|
||||
return sprintf(
|
||||
__(
|
||||
'Prevents public access to certain media files by ensuring they are only accessible via signed URLs that expire shortly after delivery. <a href="%1$s" target="_blank">How to configure private media in CloudFront</a>',
|
||||
'amazon-s3-and-cloudfront'
|
||||
),
|
||||
static::get_signed_urls_setup_doc_url()
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notice text for when a private file can't be accessed using a signed private URL.
|
||||
*
|
||||
* @param string $error_message
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_cannot_access_private_file_desc( string $error_message ): string {
|
||||
if ( false !== stripos( $error_message, 'routines::invalid digest' ) ) {
|
||||
return static::get_signing_algorithm_not_available_desc();
|
||||
}
|
||||
|
||||
return parent::get_cannot_access_private_file_desc( $error_message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Notice text for when the algorithm needed for signing CloudFront URLs is missing.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_signing_algorithm_not_available_desc(): string {
|
||||
global $as3cf;
|
||||
|
||||
$url = $as3cf::dbrains_url(
|
||||
'/wp-offload-media/doc/unsupported-cloudfront-signing-algorithm/',
|
||||
array( 'utm_campaign' => 'support+docs' )
|
||||
);
|
||||
|
||||
return sprintf(
|
||||
__(
|
||||
'It is currently not possible to serve private media from CloudFront because the server does not support the required signing algorithm. <a href="%1$s" target="_blank">Unsupported CloudFront signing algorithm</a>',
|
||||
'amazon-s3-and-cloudfront'
|
||||
),
|
||||
$url
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Notice text for when a private file can be accessed using an unsigned URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_unsigned_url_can_access_private_file_desc(): string {
|
||||
return sprintf(
|
||||
__(
|
||||
'Private media is currently exposed through unsigned URLs. Restore privacy by verifying that the <strong>%1$s</strong> matches the CloudFront behavior. <a href="%2$s" target="_blank">Read more</a>',
|
||||
'amazon-s3-and-cloudfront'
|
||||
),
|
||||
static::signed_urls_object_prefix_name(),
|
||||
static::get_signed_urls_setup_doc_url( 'create-behavior' )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Title used in various places for the Signed URLs Key ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function signed_urls_key_id_name() {
|
||||
return __( 'Public Key ID', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Description used in various places for the Signed URLs Key ID.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function signed_urls_key_id_description() {
|
||||
return __(
|
||||
"Any files set to private need a signed URL that includes the Public Key ID from a Public Key that has been added to a CloudFront distribution's Trusted Key Group.",
|
||||
'amazon-s3-and-cloudfront'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Title used in various places for the Signed URLs Key File Path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function signed_urls_key_file_path_name() {
|
||||
return __( 'Private Key File Path', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Description used in various places for the Signed URLs Key File Path.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function signed_urls_key_file_path_description() {
|
||||
return __(
|
||||
"Any files set to private need to have their URLs signed with the Private Key File whose Public Key has been uploaded to CloudFront and added to a distribution's Trusted Key Group.",
|
||||
'amazon-s3-and-cloudfront'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Description used in various places for the Signed URLs Private Object Prefix.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function signed_urls_object_prefix_description() {
|
||||
return __(
|
||||
'Any files set to private will be stored with this path prepended to the configured bucket path. An Amazon CloudFront behaviour must then be set up to restrict public access to the files at this path.',
|
||||
'amazon-s3-and-cloudfront'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function get_signed_url( Item $as3cf_item, $path, $domain, $scheme, $timestamp, $headers = array() ) {
|
||||
if ( static::use_signed_urls_key_file() ) {
|
||||
$path = $as3cf_item->private_prefix() . $path;
|
||||
|
||||
if ( $this->as3cf->private_prefix_enabled() ) {
|
||||
$item_path = $this->as3cf->maybe_update_delivery_path( $path, $domain, $timestamp );
|
||||
$item_path = AS3CF_Utils::encode_filename_in_path( $item_path );
|
||||
$private_prefix = AS3CF_Utils::trailingslash_prefix( static::get_signed_urls_object_prefix() );
|
||||
|
||||
// If object in correct private prefix, sign it.
|
||||
if ( 0 === strpos( $item_path, $private_prefix ) ) {
|
||||
$url = $scheme . '://' . $domain . '/' . $item_path;
|
||||
$key_id = static::get_signed_urls_key_id();
|
||||
$private_key_file_path = static::get_signed_urls_key_file_path();
|
||||
|
||||
$cf_url_signer = new UrlSigner( $key_id, $private_key_file_path );
|
||||
|
||||
return $cf_url_signer->getSignedUrl( $url, $timestamp );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Not set up for signing or in different private prefix, punt to default implementation.
|
||||
return parent::get_signed_url( $as3cf_item, $path, $domain, $scheme, $timestamp, $headers );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate settings for serving signed URLs.
|
||||
*
|
||||
* @return AS3CF_Result
|
||||
*/
|
||||
protected function validate_signed_url_settings(): AS3CF_Result {
|
||||
if ( ! $this->as3cf->get_setting( 'enable-signed-urls' ) ) {
|
||||
return new AS3CF_Result( Validator_Interface::AS3CF_STATUS_MESSAGE_SUCCESS );
|
||||
}
|
||||
|
||||
// Is the key ID set?
|
||||
if ( empty( $this->get_signed_urls_key_id() ) ) {
|
||||
return new AS3CF_Result(
|
||||
Validator_Interface::AS3CF_STATUS_MESSAGE_ERROR,
|
||||
sprintf(
|
||||
_x(
|
||||
'Private media cannot be delivered at the moment because required field <strong>%1$s</strong> is empty. <a href="%2$s" target="_blank">Read more</a>',
|
||||
'Delivery setting notice for issue with missing key ID',
|
||||
'amazon-s3-and-cloudfront'
|
||||
),
|
||||
static::signed_urls_key_id_name(),
|
||||
static::get_provider_service_quick_start_url() . '#configure-plugin'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$key_file_path = $this->get_signed_urls_key_file_path();
|
||||
$key_file_notice = $this->as3cf->notices->find_notice_by_id( 'validate-signed-urls-key-file-path' );
|
||||
|
||||
if ( empty( $key_file_path ) && empty( $key_file_notice ) ) {
|
||||
return new AS3CF_Result(
|
||||
Validator_Interface::AS3CF_STATUS_MESSAGE_ERROR,
|
||||
sprintf(
|
||||
_x(
|
||||
'Private media cannot be delivered at the moment because required field <strong>%1$s</strong> is empty. <a href="%2$s" target="_blank">Read more</a>',
|
||||
'Delivery setting notice for issue with empty / missing key file path',
|
||||
'amazon-s3-and-cloudfront'
|
||||
),
|
||||
static::signed_urls_key_file_path_name(),
|
||||
static::get_provider_service_quick_start_url() . '#configure-plugin'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Did the signed url key file validation trigger any issues?
|
||||
if ( ! empty( $key_file_notice ) ) {
|
||||
return new AS3CF_Result(
|
||||
Validator_Interface::AS3CF_STATUS_MESSAGE_ERROR,
|
||||
sprintf(
|
||||
_x(
|
||||
'Private media cannot be delivered at the moment because the file provided in <strong>%1$s</strong> is invalid or inaccessible. <a href="%3$s" target="_blank">Read more</a>',
|
||||
'Delivery setting notice for issue with Key File Path',
|
||||
'amazon-s3-and-cloudfront'
|
||||
),
|
||||
static::signed_urls_key_file_path_name(),
|
||||
ucfirst( $this->as3cf->notices->get_short_message( $key_file_notice ) ),
|
||||
static::get_provider_service_quick_start_url() . '#configure-plugin'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Do we have a private path?
|
||||
if ( empty( $this->get_signed_urls_object_prefix() ) ) {
|
||||
return new AS3CF_Result(
|
||||
Validator_Interface::AS3CF_STATUS_MESSAGE_ERROR,
|
||||
sprintf(
|
||||
_x(
|
||||
'Private media cannot be delivered at the moment because required field <strong>%1$s</strong> is empty. <a href="%2$s" target="_blank">Read more</a>',
|
||||
'Delivery setting notice for issue with missing Private Bucket Path',
|
||||
'amazon-s3-and-cloudfront'
|
||||
),
|
||||
static::signed_urls_object_prefix_name(),
|
||||
static::get_provider_service_quick_start_url() . '#configure-plugin'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return new AS3CF_Result( Validator_Interface::AS3CF_STATUS_MESSAGE_SUCCESS );
|
||||
}
|
||||
}
|
||||
430
classes/pro/tool.php
Normal file
430
classes/pro/tool.php
Normal file
@@ -0,0 +1,430 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro;
|
||||
|
||||
use Amazon_S3_And_CloudFront_Pro;
|
||||
use AS3CF_Pro_Utils;
|
||||
use AS3CF_Utils;
|
||||
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
||||
|
||||
abstract class Tool {
|
||||
|
||||
/**
|
||||
* @var Amazon_S3_And_CloudFront_Pro
|
||||
*/
|
||||
protected $as3cf;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $prefix = 'as3cf';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tab = 'tools';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $type = 'tool';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $view = 'tool';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
protected $priority = 0;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tool_key;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tool_slug;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $errors_key_prefix;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $errors_key;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $show_tool_constants = array();
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $requires_bucket_access = true;
|
||||
|
||||
/**
|
||||
* AS3CF_Tool constructor.
|
||||
*
|
||||
* @param Amazon_S3_And_CloudFront_Pro $as3cf
|
||||
*/
|
||||
public function __construct( $as3cf ) {
|
||||
$this->as3cf = $as3cf;
|
||||
$this->tool_slug = str_replace( array( ' ', '_' ), '-', $this->tool_key );
|
||||
$this->errors_key_prefix = 'as3cf_tool_errors_';
|
||||
$this->errors_key = $this->errors_key_prefix . $this->tool_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the tool.
|
||||
*/
|
||||
public function init() {
|
||||
add_filter( 'as3cfpro_js_strings', array( $this, 'add_js_strings' ) );
|
||||
add_filter( 'as3cfpro_js_settings', array( $this, 'add_js_settings' ) );
|
||||
|
||||
// Notices.
|
||||
add_filter( 'as3cf_get_notices', array( $this, 'maybe_add_tool_errors_to_notice' ), 10, 3 );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add strings for the Tools to the Javascript
|
||||
*
|
||||
* @param array $strings
|
||||
*
|
||||
* @return array
|
||||
*
|
||||
* Note: To be overridden by a tool if required.
|
||||
*/
|
||||
public function add_js_strings( $strings ) {
|
||||
return $strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add settings for the Tools to the Javascript
|
||||
*
|
||||
* @param array $settings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_js_settings( $settings ) {
|
||||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Priority
|
||||
*
|
||||
* @param int $priority
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function priority( $priority ) {
|
||||
$this->priority = $priority;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tools key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_tool_key() {
|
||||
return $this->tool_key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tab.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_tab() {
|
||||
return $this->tab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get info for tool, including current status.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_info() {
|
||||
return array(
|
||||
'id' => $this->tool_key,
|
||||
'tab' => $this->tab,
|
||||
'priority' => $this->priority,
|
||||
'slug' => $this->tool_slug,
|
||||
'type' => $this->type,
|
||||
'render' => $this->should_render(),
|
||||
'is_processing' => $this->is_processing(),
|
||||
'requires_bucket_access' => $this->requires_bucket_access(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Should we render the tool's UI?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_render() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Are we currently processing?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function is_processing() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is queued?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_queued(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the tool currently active, e.g. starting, working, paused or finishing up?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function is_active(): bool {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the errors created by the tool
|
||||
*
|
||||
* @param array $default
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_errors( $default = array() ) {
|
||||
return get_site_option( $this->errors_key, $default );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the saved errors for the tool
|
||||
*
|
||||
* @param array $errors
|
||||
*/
|
||||
public function update_errors( $errors ) {
|
||||
update_site_option( $this->errors_key, $errors );
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all errors created by the tool
|
||||
*/
|
||||
protected function clear_errors() {
|
||||
delete_site_option( $this->errors_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the error notice
|
||||
*
|
||||
* @param array $errors
|
||||
*/
|
||||
public function update_error_notice( $errors = array() ) {
|
||||
if ( empty( $errors ) ) {
|
||||
$errors = $this->get_errors();
|
||||
}
|
||||
|
||||
if ( ! empty( $errors ) ) {
|
||||
$args = array(
|
||||
'type' => 'error',
|
||||
'class' => 'tool-error',
|
||||
'flash' => false,
|
||||
'only_show_to_user' => false,
|
||||
'only_show_on_tab' => $this->tab,
|
||||
'custom_id' => $this->errors_key,
|
||||
'user_capabilities' => array( 'as3cfpro', 'is_plugin_setup' ),
|
||||
);
|
||||
|
||||
// Try and re-use some of existing notice to avoid churn in db or front end.
|
||||
$existing_notice = $this->as3cf->notices->find_notice_by_id( $this->errors_key );
|
||||
|
||||
if ( ! empty( $existing_notice ) ) {
|
||||
$args = array_merge( $existing_notice, $args );
|
||||
}
|
||||
|
||||
$message = $this->get_error_notice_message();
|
||||
|
||||
$this->as3cf->notices->add_notice( $message, $args );
|
||||
} else {
|
||||
$this->as3cf->notices->remove_notice_by_id( $this->errors_key );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Undismiss error notice for all users.
|
||||
*/
|
||||
public function undismiss_error_notice() {
|
||||
$this->as3cf->notices->undismiss_notice_for_all( $this->errors_key );
|
||||
}
|
||||
|
||||
/**
|
||||
* Dismiss one or all errors for a source item.
|
||||
*
|
||||
* @param int $blog_id
|
||||
* @param string $source_type
|
||||
* @param int $source_id
|
||||
* @param string|int $errors Optional indicator of which error to dismiss for source item, default 'all'.
|
||||
*/
|
||||
public function dismiss_errors( $blog_id, $source_type, $source_id, $errors = 'all' ) {
|
||||
$saved_errors = $this->get_errors();
|
||||
|
||||
foreach ( $saved_errors as $idx => &$saved_error ) {
|
||||
if ( $saved_error->blog_id !== $blog_id ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( $saved_error->source_type !== $source_type || $saved_error->source_id != $source_id ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Remove all errors for this source item?
|
||||
if ( $errors === 'all' ) {
|
||||
unset( $saved_errors[ $idx ] );
|
||||
break;
|
||||
}
|
||||
|
||||
// If the saved error message for this item is an array, remove just the one index
|
||||
if ( isset( $saved_error->messages[ $errors ] ) ) {
|
||||
// Break the object reference. See GitHub issue #2635
|
||||
$saved_error = clone $saved_error;
|
||||
unset( $saved_error->messages[ $errors ] );
|
||||
|
||||
// If the array is now empty, remove the entire error item
|
||||
if ( empty( $saved_error->messages ) ) {
|
||||
unset( $saved_errors[ $idx ] );
|
||||
} else {
|
||||
// Force a reindex of the array to avoid issues with JSON encoding switching to Object if there's non-sequential numeric keys.
|
||||
$saved_error->messages = array_values( $saved_error->messages );
|
||||
}
|
||||
}
|
||||
|
||||
// Whether we dismissed anything or not, we found and processed the expected source item's errors.
|
||||
break;
|
||||
}
|
||||
|
||||
$updated = AS3CF_Pro_Utils::array_prune_recursive( $saved_errors );
|
||||
$this->update_errors( $updated );
|
||||
$this->update_error_notice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe add error details to this tool's error notice.
|
||||
*
|
||||
* @param array $notices An array of notices.
|
||||
* @param string $tab Optionally restrict to notifications for a specific tab.
|
||||
* @param bool $all_tabs Optionally return all tab specific notices regardless of tab.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function maybe_add_tool_errors_to_notice( array $notices, $tab = '', $all_tabs = false ) {
|
||||
if ( ! empty( $notices ) ) {
|
||||
$errors = $this->get_errors();
|
||||
|
||||
if ( empty( $errors ) ) {
|
||||
return $notices;
|
||||
}
|
||||
|
||||
foreach ( $notices as $idx => $notice ) {
|
||||
if (
|
||||
! empty( $notice['class'] ) &&
|
||||
'tool-error' === $notice['class'] &&
|
||||
! empty( $notice['id'] ) &&
|
||||
$notice['id'] === $this->errors_key
|
||||
) {
|
||||
$details = array();
|
||||
foreach ( $errors as $error ) {
|
||||
// If the error is stored as an array, it's almost certainly stored
|
||||
// in a previous format/structure that we can't render properly.
|
||||
// This will be corrected by the upgrade process, but that process may
|
||||
// not have completed yet.
|
||||
if ( is_array( $error ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var Item $class */
|
||||
$class = $this->as3cf->get_source_type_class( $error->source_type );
|
||||
|
||||
$this->as3cf->switch_to_blog( $error->blog_id );
|
||||
|
||||
$details[] = array(
|
||||
'blog_id' => $error->blog_id,
|
||||
'source_type' => $error->source_type,
|
||||
'source_type_name' => $this->as3cf->get_source_type_name( $error->source_type ),
|
||||
'source_id' => $error->source_id,
|
||||
'edit_url' => $class::admin_link( $error ),
|
||||
'messages' => $error->messages,
|
||||
);
|
||||
|
||||
$this->as3cf->restore_current_blog();
|
||||
}
|
||||
$notices[ $idx ]['errors'] = array(
|
||||
'tool_key' => $this->tool_key,
|
||||
'details' => $details,
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $notices;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool specific message for error notice.
|
||||
*
|
||||
* @param string|null $message Optional message to override the default for the tool.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_error_notice_message( $message = null ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the constant used to define whether tool should always be shown (implemented as required by subclass).
|
||||
*
|
||||
* @return string|false Constant name if defined, otherwise false
|
||||
*/
|
||||
public static function show_tool_constant() {
|
||||
return AS3CF_Utils::get_first_defined_constant( static::$show_tool_constants );
|
||||
}
|
||||
|
||||
/**
|
||||
* Count media files in bucket.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
protected function count_offloaded_media_files() {
|
||||
static $count;
|
||||
|
||||
if ( is_null( $count ) ) {
|
||||
$media_counts = $this->as3cf->media_counts();
|
||||
$count = $media_counts['offloaded'];
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does the tool need authenticated access to the bucket?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function requires_bucket_access(): bool {
|
||||
return static::$requires_bucket_access;
|
||||
}
|
||||
}
|
||||
290
classes/pro/tools-manager.php
Normal file
290
classes/pro/tools-manager.php
Normal file
@@ -0,0 +1,290 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro;
|
||||
|
||||
use Amazon_S3_And_CloudFront_Pro;
|
||||
use DeliciousBrains\WP_Offload_Media\API\V1\State;
|
||||
|
||||
class Tools_Manager {
|
||||
|
||||
/**
|
||||
* @var Tools_Manager
|
||||
*/
|
||||
protected static $instance;
|
||||
|
||||
/**
|
||||
* Registered tools.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
private $tools = array();
|
||||
|
||||
/**
|
||||
* Make this class a singleton.
|
||||
*
|
||||
* Use this instead of __construct().
|
||||
*
|
||||
* @param Amazon_S3_And_CloudFront_Pro $as3cf
|
||||
*
|
||||
* @return Tools_Manager
|
||||
*/
|
||||
public static function get_instance( $as3cf ) {
|
||||
if ( ! isset( static::$instance ) && ! ( self::$instance instanceof Tools_Manager ) ) {
|
||||
static::$instance = new Tools_Manager();
|
||||
// Initialize the class
|
||||
static::$instance->init( $as3cf );
|
||||
}
|
||||
|
||||
return static::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Init.
|
||||
*
|
||||
* @param Amazon_S3_And_CloudFront_Pro $as3cf
|
||||
*/
|
||||
private function init( $as3cf ) {
|
||||
add_filter( 'as3cfpro_js_strings', array( $this, 'add_strings' ) );
|
||||
add_filter( 'cron_schedules', array( $this, 'cron_schedules' ) ); // phpcs:ignore WordPress.WP.CronInterval
|
||||
add_filter( $as3cf->get_plugin_prefix() . '_api_response_get_' . State::name(), array( $this, 'api_response' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add additional translated strings for tools.
|
||||
*
|
||||
* @param array $strings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_strings( $strings ) {
|
||||
return array_merge( $strings, array(
|
||||
'pause_button' => _x( 'Pause', 'Button text', 'amazon-s3-and-cloudfront' ),
|
||||
'resume_button' => _x( 'Resume', 'Button text', 'amazon-s3-and-cloudfront' ),
|
||||
'disabled_tool_button' => _x( 'Disabled because another background process is running.', 'Disabled button tooltip', 'amazon-s3-and-cloudfront' ),
|
||||
'disabled_tool_bucket_access' => _x( 'Disabled because this tool requires write access to the bucket.', 'Disabled button tooltip', 'amazon-s3-and-cloudfront' ),
|
||||
'item' => __( 'Item', 'amazon-s3-and-cloudfront' ),
|
||||
'media_library_item' => _x( 'Media Library Item', 'Source type for item error', 'amazon-s3-and-cloudfront' ),
|
||||
'edit_item' => _x( 'Edit', 'Link title', 'amazon-s3-and-cloudfront' ),
|
||||
'dismiss_all' => _x( 'Dismiss All', 'Link title', 'amazon-s3-and-cloudfront' ),
|
||||
'dismiss' => _x( 'Dismiss', 'Link title', 'amazon-s3-and-cloudfront' ),
|
||||
|
||||
// No tools to show yet
|
||||
'no_tools_header' => _x( 'No Tools Available (Yet)', 'No tools graphic', 'amazon-s3-and-cloudfront' ),
|
||||
'no_tools_description' => _x( 'Once an active license and media items are detected, the following tools will become available for bulk management between this server and the storage provider.', 'No tools graphic', 'amazon-s3-and-cloudfront' ),
|
||||
) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add cron schedules.
|
||||
*
|
||||
* @param array $schedules
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function cron_schedules( $schedules ) {
|
||||
if ( property_exists( $this, 'cron_interval' ) ) {
|
||||
$interval = apply_filters( 'as3cf_tool_cron_interval', $this->cron_interval );
|
||||
} else {
|
||||
$interval = apply_filters( 'as3cf_tool_cron_interval', 1 );
|
||||
}
|
||||
|
||||
if ( 1 === $interval ) {
|
||||
$display = __( 'Every Minute', 'amazon-s3-and-cloudfront' );
|
||||
} else {
|
||||
$display = sprintf( __( 'Every %d Minutes', 'amazon-s3-and-cloudfront' ), $interval );
|
||||
}
|
||||
|
||||
// Adds our schedule to the existing schedules.
|
||||
$schedules['as3cf_tool_cron_interval'] = array(
|
||||
'interval' => MINUTE_IN_SECONDS * $interval,
|
||||
'display' => $display,
|
||||
);
|
||||
|
||||
return $schedules;
|
||||
}
|
||||
|
||||
/**
|
||||
* If a get state API response is an array without any info for tools, add tools info.
|
||||
*
|
||||
* @handles as3cf_api_response_get_state
|
||||
*
|
||||
* @param mixed $response
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function api_response( $response ) {
|
||||
if ( empty( $response ) || is_wp_error( $response ) || ! is_array( $response ) || isset( $response['tools'] ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$response['tools'] = $this->get_tools_info();
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a tool.
|
||||
*
|
||||
* @param Tool $tool
|
||||
* @param string $context
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function register_tool( Tool $tool, $context = 'background' ) {
|
||||
if ( ! empty( $this->tools[ $context ][ $tool->get_tool_key() ] ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->tools[ $context ][ $tool->get_tool_key() ] = $tool;
|
||||
|
||||
$tool->priority( $this->get_tools_count() )->init();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tool.
|
||||
*
|
||||
* @param string $name
|
||||
*
|
||||
* @return bool|Tool
|
||||
*/
|
||||
public function get_tool( $name ) {
|
||||
foreach ( $this->tools as $context ) {
|
||||
if ( array_key_exists( $name, $context ) ) {
|
||||
return $context[ $name ];
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tools count.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function get_tools_count() {
|
||||
$count = 0;
|
||||
|
||||
foreach ( $this->tools as $context ) {
|
||||
$count += count( $context );
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all tools.
|
||||
*
|
||||
* @param string $context Optional context to restrict by.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_tools( $context = null ) {
|
||||
$result = array();
|
||||
|
||||
foreach ( $this->tools as $_context => $tools ) {
|
||||
if ( ! empty( $context ) && $_context !== $context ) {
|
||||
continue;
|
||||
}
|
||||
/**
|
||||
* @var string $key
|
||||
* @var Tool $tool
|
||||
*/
|
||||
foreach ( $tools as $key => $tool ) {
|
||||
$result[ $key ] = $tool;
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get tool's info, including current status.
|
||||
*
|
||||
* @param string $context Optional context to restrict by.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_tools_info( $context = null ) {
|
||||
$data = array();
|
||||
|
||||
/** @var Tool $tool */
|
||||
foreach ( $this->get_tools( $context ) as $key => $tool ) {
|
||||
$data[ $key ] = $tool->get_info();
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get running tool, or null if none running.
|
||||
*
|
||||
* @param string $context Optional context to restrict by.
|
||||
*
|
||||
* @return Tool|null
|
||||
*/
|
||||
public function get_running_tool( $context = null ) {
|
||||
/** @var Tool $tool */
|
||||
foreach ( $this->get_tools( $context ) as $tool ) {
|
||||
$data = $tool->get_info();
|
||||
|
||||
if ( ! empty( $data['is_processing'] ) || ! empty( $data['is_queued'] ) || ! empty( $data['is_paused'] ) ) {
|
||||
return $tool;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Try and perform the requested action for a tool identified by its key.
|
||||
*
|
||||
* @param string $tool_key
|
||||
* @param string $action
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function perform_action( $tool_key, $action ) {
|
||||
$tool = $this->get_tool( $tool_key );
|
||||
|
||||
if ( false === $tool ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! method_exists( $tool, 'handle_' . $action ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$running_tool = $this->get_running_tool();
|
||||
|
||||
// Only one tool can be running or interacted with at once.
|
||||
if ( ! empty( $running_tool ) && $tool->get_tool_key() !== $running_tool->get_tool_key() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
call_user_func( array( $tool, 'handle_' . $action ) );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Protected constructor to prevent creating a new instance of the
|
||||
* class via the `new` operator from outside this class.
|
||||
*/
|
||||
protected function __construct() {
|
||||
}
|
||||
|
||||
/**
|
||||
* As this class is a singleton it should not be clone-able.
|
||||
*/
|
||||
protected function __clone() {
|
||||
}
|
||||
|
||||
/**
|
||||
* As this class is a singleton it should not be able to be un-serialized.
|
||||
*/
|
||||
public function __wakeup() {
|
||||
}
|
||||
}
|
||||
157
classes/pro/tools/add-metadata.php
Normal file
157
classes/pro/tools/add-metadata.php
Normal file
@@ -0,0 +1,157 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Tools;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Add_Metadata_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Background_Tool_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Tool;
|
||||
|
||||
class Add_Metadata extends Background_Tool {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tool_key = 'add_metadata';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $show_tool_constants = array(
|
||||
'AS3CF_SHOW_ADD_METADATA_TOOL',
|
||||
);
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $requires_bucket_access = false;
|
||||
|
||||
/**
|
||||
* Limit the item types that this tool handles.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $source_types = array(
|
||||
'media-library',
|
||||
);
|
||||
|
||||
/**
|
||||
* Get a list of key names for tools that are related to the current tool.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_related_tools() {
|
||||
$last_started = get_site_option( $this->prefix . '_' . $this->get_tool_key() . '_last_started' );
|
||||
|
||||
if ( empty( $last_started ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return array(
|
||||
'reverse_add_metadata',
|
||||
'verify_add_metadata',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle start.
|
||||
*/
|
||||
public function handle_start() {
|
||||
update_site_option( $this->prefix . '_' . $this->get_tool_key() . '_last_started', time() );
|
||||
|
||||
parent::handle_start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Should render.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_render() {
|
||||
if ( ! $this->as3cf->is_pro_plugin_setup() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( false !== static::show_tool_constant() && constant( static::show_tool_constant() ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->is_active();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tool's name. Defaults to its title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return __( 'Add Metadata', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text() {
|
||||
return __( 'Add metadata for all media not offloaded', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more info text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_more_info_text() {
|
||||
return htmlspecialchars( __( "If you already have your site's media in a bucket in the cloud, you can configure WP Offload Media for this bucket and storage paths, then run this tool to go through all your media that hasn't been offloaded yet and add the metadata WP Offload Media needs to manage your media in that bucket and rewrite URLs. After this tool runs, it will give you an undo option (remove all metadata added by this tool) and an option to go through all the media items that we added metadata for, check that the files exist in the bucket, and remove any new metadata where files are missing.", 'amazon-s3-and-cloudfront' ), ENT_QUOTES );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_button_text() {
|
||||
return __( 'Add Metadata', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_queued_status(): string {
|
||||
return __( 'Adding metadata to Media Library', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_short_queued_status(): string {
|
||||
return _x( 'Adding metadata…', 'Short tool running message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Message for error notice
|
||||
*
|
||||
* @param string|null $message Optional message to override the default for the tool.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_error_notice_message( $message = null ) {
|
||||
$title = __( 'Add Metadata Errors', 'amazon-s3-and-cloudfront' );
|
||||
$message = empty( $message ) ? __( 'Previous attempts at adding metadata to your media library have resulted in errors.', 'amazon-s3-and-cloudfront' ) : $message;
|
||||
|
||||
return sprintf( '<strong>%s</strong> — %s', $title, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get background process class.
|
||||
*
|
||||
* @return Background_Tool_Process|null
|
||||
*/
|
||||
protected function get_background_process_class() {
|
||||
return new Add_Metadata_Process( $this->as3cf, $this );
|
||||
}
|
||||
}
|
||||
89
classes/pro/tools/analyze-and-repair.php
Normal file
89
classes/pro/tools/analyze-and-repair.php
Normal file
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Tools;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Tool;
|
||||
|
||||
abstract class Analyze_And_Repair extends Background_Tool {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tool_key = 'analyze_and_repair';
|
||||
|
||||
/**
|
||||
* Limit the item types that this tool handles.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $source_types = array(
|
||||
'media-library',
|
||||
);
|
||||
|
||||
/**
|
||||
* Should render.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_render() {
|
||||
if ( ! $this->as3cf->is_pro_plugin_setup() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( false !== static::show_tool_constant() && constant( static::show_tool_constant() ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->is_active();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text() {
|
||||
return __( 'Analyze & Repair', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_button_text() {
|
||||
return __( 'Analyze & Repair', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_queued_status(): string {
|
||||
return __( 'Analyzing and repairing offload metadata.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_short_queued_status(): string {
|
||||
return _x( 'Repairing…', 'Short tool running message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Message for error notice
|
||||
*
|
||||
* @param string|null $message Optional message to override the default for the tool.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_error_notice_message( $message = null ) {
|
||||
$title = __( 'Analyze & Repair Errors', 'amazon-s3-and-cloudfront' );
|
||||
$message = empty( $message ) ? __( 'Previous attempts at analyzing and repairing your offload metadata have resulted in errors.', 'amazon-s3-and-cloudfront' ) : $message;
|
||||
|
||||
return sprintf( '<strong>%s</strong> — %s', $title, $message );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Tools\Analyze_And_Repair;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Analyze_And_Repair\Reverse_Add_Metadata_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Background_Tool_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Tools\Analyze_And_Repair;
|
||||
|
||||
class Reverse_Add_Metadata extends Analyze_And_Repair {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tool_key = 'reverse_add_metadata';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $show_tool_constants = array(
|
||||
'AS3CF_SHOW_REVERSE_ADD_METADATA_TOOL',
|
||||
);
|
||||
|
||||
/**
|
||||
* Get title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text() {
|
||||
return __( 'Remove metadata added by the Add Metadata tool', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more info text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_more_info_text() {
|
||||
return __( 'If you have previously used the Add Metadata tool to create new items but now wish to remove all those records, you can use this tool. It will not remove any offload metadata not created with the Add Metadata tool such as regular Media Library offloads.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_button_text() {
|
||||
return __( 'Remove Metadata', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_queued_status(): string {
|
||||
return __( 'Removing metadata added by the Add Metadata tool.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_short_queued_status(): string {
|
||||
return _x( 'Removing metadata…', 'Short tool running message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get background process class.
|
||||
*
|
||||
* @return Background_Tool_Process|null
|
||||
*/
|
||||
protected function get_background_process_class() {
|
||||
return new Reverse_Add_Metadata_Process( $this->as3cf, $this );
|
||||
}
|
||||
}
|
||||
67
classes/pro/tools/analyze-and-repair/verify-add-metadata.php
Normal file
67
classes/pro/tools/analyze-and-repair/verify-add-metadata.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Tools\Analyze_And_Repair;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Analyze_And_Repair\Verify_Add_Metadata_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Background_Tool_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Tools\Analyze_And_Repair;
|
||||
|
||||
class Verify_Add_Metadata extends Analyze_And_Repair {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tool_key = 'verify_add_metadata';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $show_tool_constants = array(
|
||||
'AS3CF_SHOW_VERIFY_ADD_METADATA_TOOL',
|
||||
);
|
||||
|
||||
/**
|
||||
* Get title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text() {
|
||||
return __( 'Find items with files missing in bucket and remove metadata, mark others verified', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more info text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_more_info_text() {
|
||||
return __( 'You can use this tool to check that files exist in the bucket for items recently created with the Add Metadata tool. New metadata items with files missing from the bucket will be removed. It will not remove any offload metadata not created with the Add Metadata tool such as regular Media Library offloads.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_queued_status(): string {
|
||||
return __( 'Finding items with files missing in bucket and removing their metadata, marking others as verified.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_short_queued_status(): string {
|
||||
return _x( 'Verifying…', 'Short tool running message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get background process class.
|
||||
*
|
||||
* @return Background_Tool_Process|null
|
||||
*/
|
||||
protected function get_background_process_class() {
|
||||
return new Verify_Add_Metadata_Process( $this->as3cf, $this );
|
||||
}
|
||||
}
|
||||
152
classes/pro/tools/copy-buckets.php
Normal file
152
classes/pro/tools/copy-buckets.php
Normal file
@@ -0,0 +1,152 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Tools;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Background_Tool_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Copy_Buckets_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Tool;
|
||||
|
||||
class Copy_Buckets extends Background_Tool {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tool_key = 'copy_buckets';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tab = 'media';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $show_tool_constants = array(
|
||||
'AS3CF_SHOW_COPY_BUCKETS_TOOL',
|
||||
'WPOS3_SHOW_COPY_BUCKETS_TOOL',
|
||||
);
|
||||
|
||||
/**
|
||||
* Should render.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_render() {
|
||||
if ( ! $this->as3cf->is_pro_plugin_setup() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( false !== static::show_tool_constant() && constant( static::show_tool_constant() ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->is_active();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return __( 'Copy Files', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text() {
|
||||
return __( 'Copy files to new bucket', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_button_text() {
|
||||
return __( 'Begin Copy', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_queued_status(): string {
|
||||
return __( 'Copying media items between buckets.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_short_queued_status(): string {
|
||||
return _x( 'Copying…', 'Short tool running message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more info text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_more_info_text() {
|
||||
return __( 'Would you like to consolidate your offloaded media files by copying them into the currently selected bucket? All existing offloaded media URLs will be updated to reference the new bucket.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prompt text for when tool could be run in response to settings change.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_prompt_text() {
|
||||
global $as3cf;
|
||||
|
||||
$mesg = __( 'Would you like to copy media files from their current bucket to this new bucket?', 'amazon-s3-and-cloudfront' );
|
||||
$mesg .= ' ';
|
||||
$mesg .= $as3cf::more_info_link(
|
||||
'/wp-offload-media/doc/how-to-copy-media-between-buckets/',
|
||||
'copy+buckets',
|
||||
'change'
|
||||
);
|
||||
|
||||
return $mesg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function get_doc_url() {
|
||||
global $as3cf;
|
||||
|
||||
$args = array( 'utm_campaign' => 'copy+buckets' );
|
||||
|
||||
return $as3cf::dbrains_url( '/wp-offload-media/doc/how-to-copy-media-between-buckets/', $args, 'change' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Message for error notice.
|
||||
*
|
||||
* @param string|null $message Optional message to override the default for the tool.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_error_notice_message( $message = null ) {
|
||||
$title = __( 'Copy Bucket Errors', 'amazon-s3-and-cloudfront' );
|
||||
$message = empty( $message ) ? __( 'Previous attempts at copying your media library between buckets have resulted in errors.', 'amazon-s3-and-cloudfront' ) : $message;
|
||||
|
||||
return sprintf( '<strong>%s</strong> — %s', $title, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get background process class.
|
||||
*
|
||||
* @return Background_Tool_Process|null
|
||||
*/
|
||||
protected function get_background_process_class() {
|
||||
return new Copy_Buckets_Process( $this->as3cf, $this );
|
||||
}
|
||||
}
|
||||
95
classes/pro/tools/download-and-remover.php
Normal file
95
classes/pro/tools/download-and-remover.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Tools;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Background_Tool_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Download_And_Remover_Process;
|
||||
|
||||
class Download_And_Remover extends Downloader {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tool_key = 'download_and_remover';
|
||||
|
||||
/**
|
||||
* Message for error notice
|
||||
*
|
||||
* @param string|null $message Optional message to override the default for the tool.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_error_notice_message( $message = null ) {
|
||||
$title = __( 'Removal Errors', 'amazon-s3-and-cloudfront' );
|
||||
$message = empty( $message ) ? __( 'Previous attempts at removing your media library from the bucket have resulted in errors.', 'amazon-s3-and-cloudfront' ) : $message;
|
||||
|
||||
return sprintf( '<strong>%s</strong> — %s', $title, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Should render.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_render() {
|
||||
if ( ! $this->as3cf->is_plugin_setup() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $this->count_offloaded_media_files();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text() {
|
||||
return __( 'Remove all files from bucket', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more info text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_more_info_text() {
|
||||
return __( 'This tool goes through all your media and deletes files from the bucket. If the file doesn\'t exist on your server, it will download it before deleting.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_button_text() {
|
||||
return __( 'Remove Files', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_queued_status(): string {
|
||||
return __( 'Removing media from bucket', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_short_queued_status(): string {
|
||||
return _x( 'Removing…', 'Short tool running message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get background process class.
|
||||
*
|
||||
* @return Background_Tool_Process|null
|
||||
*/
|
||||
protected function get_background_process_class() {
|
||||
return new Download_And_Remover_Process( $this->as3cf, $this );
|
||||
}
|
||||
}
|
||||
183
classes/pro/tools/downloader.php
Normal file
183
classes/pro/tools/downloader.php
Normal file
@@ -0,0 +1,183 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Tools;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Background_Tool_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Downloader_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Tool;
|
||||
|
||||
class Downloader extends Background_Tool {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tool_key = 'downloader';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $deactivate_prompt_rendered = false;
|
||||
|
||||
/**
|
||||
* Initialize Downloader
|
||||
*/
|
||||
public function init() {
|
||||
parent::init();
|
||||
|
||||
if ( ! $this->as3cf->is_pro_plugin_setup( true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->maybe_render_deactivate_prompt();
|
||||
}
|
||||
|
||||
/**
|
||||
* Maybe render deactivate plugin prompt.
|
||||
*/
|
||||
private function maybe_render_deactivate_prompt() {
|
||||
if ( self::$deactivate_prompt_rendered ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( ! $this->as3cf->get_setting( 'remove-local-file' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
add_action( 'load-plugins.php', array( $this, 'deactivate_plugin_assets' ) );
|
||||
add_action( 'admin_footer', array( $this, 'deactivate_plugin_modal' ) );
|
||||
|
||||
self::$deactivate_prompt_rendered = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the modal for plugin deactivation
|
||||
*
|
||||
* TODO: Could be replaced with Modal.svelte using REST-API for modal button's actions etc.
|
||||
*/
|
||||
public function deactivate_plugin_assets() {
|
||||
$this->as3cf->enqueue_script(
|
||||
'as3cf-pro-downloader-deactivate-plugin-script',
|
||||
'assets/js/pro/tools/downloader-deactivate-plugin',
|
||||
array( 'as3cf-modal', 'wp-api-request' )
|
||||
);
|
||||
|
||||
wp_localize_script( 'as3cf-pro-downloader-deactivate-plugin-script', 'as3cfpro_downloader', array(
|
||||
'plugin_url' => $this->as3cf->get_plugin_page_url( array( 'hash' => '/tools' ) ),
|
||||
'plugin_slug' => $this->as3cf->get_plugin_row_slug(),
|
||||
) );
|
||||
|
||||
wp_enqueue_style( 'as3cf-modal' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the view for the plugin deactivation modal
|
||||
*/
|
||||
public function deactivate_plugin_modal() {
|
||||
global $pagenow;
|
||||
if ( 'plugins.php' !== $pagenow ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->as3cf->render_view( 'deactivate-plugin-prompt' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Message for error notice
|
||||
*
|
||||
* @param string|null $message Optional message to override the default for the tool.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_error_notice_message( $message = null ) {
|
||||
$title = __( 'Download Errors', 'amazon-s3-and-cloudfront' );
|
||||
$message = empty( $message ) ? __( 'Previous attempts at downloading your media library from the bucket have resulted in errors.', 'amazon-s3-and-cloudfront' ) : $message;
|
||||
|
||||
return sprintf( '<strong>%s</strong> — %s', $title, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Should render.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_render() {
|
||||
if ( ! $this->as3cf->is_pro_plugin_setup() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $this->count_offloaded_media_files();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text() {
|
||||
return __( 'Download all files from bucket to server', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more info text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_more_info_text() {
|
||||
return __( 'If you\'ve ever had the "Remove Local Media" option on, some media files are likely missing on your server. You can use this tool to download any missing files back to your server.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prompt text for when tool could be run in response to settings change.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_prompt_text() {
|
||||
global $as3cf;
|
||||
|
||||
$mesg = __( 'You\'ve disabled the "Remove Local Media" option. Do you want to download all media files previously offloaded and removed from the server? This tool will not remove the media files from the bucket, and only downloads files that are missing from the server.', 'amazon-s3-and-cloudfront' );
|
||||
$mesg .= ' ';
|
||||
$mesg .= $as3cf::settings_more_info_link(
|
||||
'remove-local-file',
|
||||
'',
|
||||
'remove+local+file'
|
||||
);
|
||||
|
||||
return $mesg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_button_text() {
|
||||
return __( 'Download Files', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_queued_status(): string {
|
||||
return __( 'Downloading media from bucket', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_short_queued_status(): string {
|
||||
return _x( 'Downloading…', 'Short tool running message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get background process class.
|
||||
*
|
||||
* @return Background_Tool_Process|null
|
||||
*/
|
||||
protected function get_background_process_class() {
|
||||
return new Downloader_Process( $this->as3cf, $this );
|
||||
}
|
||||
}
|
||||
213
classes/pro/tools/elementor-analyze-and-repair.php
Normal file
213
classes/pro/tools/elementor-analyze-and-repair.php
Normal file
@@ -0,0 +1,213 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Tools;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Background_Tool_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Elementor_Analyze_And_Repair_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Tool;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Integrations\Elementor;
|
||||
|
||||
class Elementor_Analyze_And_Repair extends Background_Tool {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tool_key = 'elementor_analyze_and_repair';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $requires_bucket_access = false;
|
||||
|
||||
/**
|
||||
* Limit the item types that this tool handles.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $source_types = array(
|
||||
'media-library',
|
||||
);
|
||||
|
||||
/**
|
||||
* Initialize tool
|
||||
*/
|
||||
public function init() {
|
||||
parent::init();
|
||||
|
||||
if ( ! $this->as3cf->is_pro_plugin_setup( true ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->maybe_show_notice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a notice about this tool to the user under certain conditions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
private function maybe_show_notice() {
|
||||
if ( wp_doing_ajax() || ! Elementor::is_installed() ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$notice_id = 'elementor-analyze-and-repair';
|
||||
$tool_completed_id = $this->get_tool_key() . '_completed';
|
||||
$tool_not_needed_key = $this->prefix . '_' . $this->get_tool_key() . '_not_needed';
|
||||
$tool_not_needed = get_site_option( $tool_not_needed_key ) !== false;
|
||||
|
||||
// If the tool has already run or we previously deemed it's not needed just ensure the
|
||||
// notice isn't there, removing it if needed.
|
||||
$tool_completed_notice = $this->as3cf->notices->find_notice_by_id( $tool_completed_id );
|
||||
if ( ! empty( $tool_completed_notice ) || $tool_not_needed ) {
|
||||
$this->as3cf->notices->remove_notice_by_id( $notice_id );
|
||||
if ( false === $tool_not_needed ) {
|
||||
update_site_option( $tool_not_needed_key, time() );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Exit out if the notice already exists
|
||||
$notice = $this->as3cf->notices->find_notice_by_id( $notice_id );
|
||||
if ( ! empty( $notice ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// count posts with elementor and offloaded media files
|
||||
$media_counts = $this->as3cf->media_counts();
|
||||
$process_object = $this->get_background_process_class();
|
||||
$elementor_items = $process_object->get_elementor_items_count();
|
||||
|
||||
// Either OME or Elementor have never been used, there can't be an overlap of items.
|
||||
if ( 0 === $media_counts['offloaded'] || 0 === $elementor_items ) {
|
||||
update_site_option( $tool_not_needed_key, time() );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->as3cf->notices->add_notice(
|
||||
sprintf(
|
||||
__(
|
||||
'There is content created with Elementor
|
||||
that may need to be checked for broken media links. Go to the WP Offload Media settings page and run the
|
||||
Elementor Analyze and repair tool. <a href="%s">Get started »</a>',
|
||||
'amazon-s3-and-cloudfront'
|
||||
),
|
||||
$this->as3cf->get_plugin_page_url()
|
||||
),
|
||||
array(
|
||||
'custom_id' => $notice_id,
|
||||
'type' => 'notice-info',
|
||||
'flash' => false,
|
||||
'only_show_to_user' => false,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Message for error notice
|
||||
*
|
||||
* @param string|null $message Optional message to override the default for the tool.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_error_notice_message( $message = null ) {
|
||||
$title = __( 'Elementor Analyze and Repair errors', 'amazon-s3-and-cloudfront' );
|
||||
$message = empty( $message ) ? __( 'Previous attempts at analyzing and repairing Elementor content resulted in errors', 'amazon-s3-and-cloudfront' ) : $message;
|
||||
|
||||
return sprintf( '<strong>%s</strong> — %s', $title, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Should render.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_render() {
|
||||
if ( ! $this->as3cf->is_pro_plugin_setup() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( defined( 'AS3CF_SHOW_ELEMENTOR_TOOL' ) && AS3CF_SHOW_ELEMENTOR_TOOL ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( defined( 'AS3CF_SHOW_ELEMENTOR_TOOL' ) && ! AS3CF_SHOW_ELEMENTOR_TOOL ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! defined( 'ELEMENTOR_VERSION' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $this->count_offloaded_media_files();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tool's name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return __( 'Elementor Analyze & Repair', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text() {
|
||||
return __( 'Analyze and repair content created with Elementor', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more info text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_more_info_text() {
|
||||
return __(
|
||||
'This tools goes through all content created with Elementor. It will check all media URLs found and if
|
||||
needed repair any broken URLs to offloaded media files.',
|
||||
'amazon-s3-and-cloudfront'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_button_text() {
|
||||
return __( 'Analyze and repair', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_queued_status(): string {
|
||||
return __( 'Analyze and repair URLs in Elementor content', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_short_queued_status(): string {
|
||||
return _x( 'Repairing…', 'Short tool running message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get background process class.
|
||||
*
|
||||
* @return Background_Tool_Process|null
|
||||
*/
|
||||
protected function get_background_process_class() {
|
||||
return new Elementor_Analyze_And_Repair_Process( $this->as3cf, $this );
|
||||
}
|
||||
}
|
||||
118
classes/pro/tools/move-objects.php
Normal file
118
classes/pro/tools/move-objects.php
Normal file
@@ -0,0 +1,118 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Tools;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Background_Tool_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Move_Objects_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Tool;
|
||||
|
||||
class Move_Objects extends Background_Tool {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tool_key = 'move_objects';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $show_tool_constants = array(
|
||||
'AS3CF_SHOW_MOVE_OBJECTS_TOOL',
|
||||
);
|
||||
|
||||
/**
|
||||
* Should render.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_render() {
|
||||
if ( ! $this->as3cf->is_pro_plugin_setup() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( false !== static::show_tool_constant() && constant( static::show_tool_constant() ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->is_active();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text() {
|
||||
return __( 'Move files to new path prefix', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_button_text() {
|
||||
return __( 'Begin Move', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_queued_status(): string {
|
||||
return __( 'Moving media items to new path prefix.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_short_queued_status(): string {
|
||||
return _x( 'Moving…', 'Short tool running message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more info text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_more_info_text() {
|
||||
return __( 'Would you like to move your offloaded media files to paths that match the current path prefix settings? All existing offloaded media URLs will be updated to reference the new paths.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function get_doc_url() {
|
||||
global $as3cf;
|
||||
|
||||
$args = array( 'utm_campaign' => 'move+objects' );
|
||||
|
||||
return $as3cf::dbrains_url( '/wp-offload-media/doc/how-to-move-media-to-a-new-bucket-path/', $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Message for error notice.
|
||||
*
|
||||
* @param string|null $message Optional message to override the default for the tool.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_error_notice_message( $message = null ) {
|
||||
$title = __( 'Move Objects Errors', 'amazon-s3-and-cloudfront' );
|
||||
$message = empty( $message ) ? __( 'Previous attempts at moving your media library to new paths have resulted in errors.', 'amazon-s3-and-cloudfront' ) : $message;
|
||||
|
||||
return sprintf( '<strong>%1$s</strong> — %2$s', $title, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get background process class.
|
||||
*
|
||||
* @return Background_Tool_Process|null
|
||||
*/
|
||||
protected function get_background_process_class() {
|
||||
return new Move_Objects_Process( $this->as3cf, $this );
|
||||
}
|
||||
}
|
||||
106
classes/pro/tools/move-private-objects.php
Normal file
106
classes/pro/tools/move-private-objects.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Tools;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Background_Tool_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Move_Private_Objects_Process;
|
||||
|
||||
class Move_Private_Objects extends Move_Objects {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tool_key = 'move_private_objects';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $show_tool_constants = array(
|
||||
'AS3CF_SHOW_MOVE_PRIVATE_OBJECTS_TOOL',
|
||||
);
|
||||
|
||||
/**
|
||||
* Get title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text() {
|
||||
return __( 'Move files to new private path', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_queued_status(): string {
|
||||
return __( 'Moving media items to new private path.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more info text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_more_info_text() {
|
||||
return __( 'Would you like to move your offloaded media files to paths that match the current private path settings? All existing offloaded private media URLs will be updated to reference the new paths.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prompt text for when tool could be run in response to settings change.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_prompt_text() {
|
||||
global $as3cf;
|
||||
|
||||
$mesg = '<h3>' . __( 'Private Path Updated: Would you like to move existing media to the new private path?', 'amazon-s3-and-cloudfront' ) . '</h3>';
|
||||
$mesg .= '<br>';
|
||||
$mesg .= '<p>' . __( 'You just updated the private media path. Any media you make private from now on will use this new path.', 'amazon-s3-and-cloudfront' ) . '</p>';
|
||||
$mesg .= '<p>';
|
||||
$mesg .= __( 'You can also move existing private media to this new path. We recommend keeping the path consistent across all private media.', 'amazon-s3-and-cloudfront' );
|
||||
$mesg .= ' ';
|
||||
$mesg .= $as3cf::more_info_link(
|
||||
'/wp-offload-media/doc/how-to-move-media-to-a-new-bucket-path/',
|
||||
'move+objects',
|
||||
'private-path'
|
||||
);
|
||||
$mesg .= '</p>';
|
||||
|
||||
return $mesg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function get_doc_url() {
|
||||
global $as3cf;
|
||||
|
||||
$args = array( 'utm_campaign' => 'move+objects' );
|
||||
|
||||
return $as3cf::dbrains_url( '/wp-offload-media/doc/how-to-move-media-to-a-new-bucket-path/', $args, 'private-path' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Message for error notice.
|
||||
*
|
||||
* @param string|null $message Optional message to override the default for the tool.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_error_notice_message( $message = null ) {
|
||||
$title = __( 'Move Objects Errors', 'amazon-s3-and-cloudfront' );
|
||||
$message = empty( $message ) ? __( 'Previous attempts at moving your media library to new private paths have resulted in errors.', 'amazon-s3-and-cloudfront' ) : $message;
|
||||
|
||||
return sprintf( '<strong>%1$s</strong> — %2$s', $title, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get background process class.
|
||||
*
|
||||
* @return Background_Tool_Process|null
|
||||
*/
|
||||
protected function get_background_process_class() {
|
||||
return new Move_Private_Objects_Process( $this->as3cf, $this );
|
||||
}
|
||||
}
|
||||
125
classes/pro/tools/move-public-objects.php
Normal file
125
classes/pro/tools/move-public-objects.php
Normal file
@@ -0,0 +1,125 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Tools;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Background_Tool_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Move_Public_Objects_Process;
|
||||
|
||||
class Move_Public_Objects extends Move_Objects {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tool_key = 'move_public_objects';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $show_tool_constants = array(
|
||||
'AS3CF_SHOW_MOVE_PUBLIC_OBJECTS_TOOL',
|
||||
);
|
||||
|
||||
/**
|
||||
* Add notice strings for when tool will not prompt.
|
||||
*
|
||||
* @param array $strings
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function add_js_strings( $strings ) {
|
||||
global $as3cf;
|
||||
|
||||
$notice_link = $as3cf::more_info_link( '/wp-offload-media/doc/how-to-move-media-to-a-new-bucket-path/', 'error-media+move+objects' );
|
||||
$notice_msg = __( '<strong>Warning</strong> — Because you\'ve turned off %1$s, we will not offer to move existing media to the new path as it could result in overwriting some media in your bucket. %2$s', 'amazon-s3-and-cloudfront' );
|
||||
|
||||
$strings['no_move_objects_year_month_notice'] = sprintf( $notice_msg, __( 'Year/Month', 'amazon-s3-and-cloudfront' ), $notice_link );
|
||||
$strings['no_move_objects_object_versioning_notice'] = sprintf( $notice_msg, __( 'Object Versioning', 'amazon-s3-and-cloudfront' ), $notice_link );
|
||||
|
||||
return $strings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text() {
|
||||
return __( 'Move files to new storage path', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_queued_status(): string {
|
||||
return __( 'Moving media items to new storage path.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more info text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_more_info_text() {
|
||||
return __( 'Would you like to move your offloaded media files to paths that match the current storage path settings? All existing offloaded media URLs will be updated to reference the new paths.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prompt text for when tool could be run in response to settings change.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_prompt_text() {
|
||||
global $as3cf;
|
||||
|
||||
$mesg = '<h3>' . __( 'Storage Path Updated: Would you like to move existing media to the new path?', 'amazon-s3-and-cloudfront' ) . '</h3>';
|
||||
$mesg .= '<br>';
|
||||
$mesg .= '<p>' . __( 'You just updated the storage path. Any new media you offload from now on will use this new path.', 'amazon-s3-and-cloudfront' ) . '</p>';
|
||||
$mesg .= '<p>';
|
||||
$mesg .= __( 'You can also move existing media to this new path. Beware however that moving existing media will update URLs and will result in 404s for any sites embedding or linking to your media. It could also have a negative impact on SEO. If you\'re unsure about this, we recommend not moving existing media to the new path.', 'amazon-s3-and-cloudfront' );
|
||||
$mesg .= ' ';
|
||||
$mesg .= $as3cf::more_info_link(
|
||||
'/wp-offload-media/doc/how-to-move-media-to-a-new-bucket-path/',
|
||||
'move+objects',
|
||||
'public-path'
|
||||
);
|
||||
$mesg .= '</p>';
|
||||
|
||||
return $mesg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function get_doc_url() {
|
||||
global $as3cf;
|
||||
|
||||
$args = array( 'utm_campaign' => 'move+objects' );
|
||||
|
||||
return $as3cf::dbrains_url( '/wp-offload-media/doc/how-to-move-media-to-a-new-bucket-path/', $args, 'public-path' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Message for error notice.
|
||||
*
|
||||
* @param string|null $message Optional message to override the default for the tool.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_error_notice_message( $message = null ) {
|
||||
$title = __( 'Move Objects Errors', 'amazon-s3-and-cloudfront' );
|
||||
$message = empty( $message ) ? __( 'Previous attempts at moving your media library to new storage paths have resulted in errors.', 'amazon-s3-and-cloudfront' ) : $message;
|
||||
|
||||
return sprintf( '<strong>%1$s</strong> — %2$s', $title, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get background process class.
|
||||
*
|
||||
* @return Background_Tool_Process|null
|
||||
*/
|
||||
protected function get_background_process_class() {
|
||||
return new Move_Public_Objects_Process( $this->as3cf, $this );
|
||||
}
|
||||
}
|
||||
120
classes/pro/tools/remove-local-files.php
Normal file
120
classes/pro/tools/remove-local-files.php
Normal file
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Tools;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Background_Tool_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Remove_Local_Files_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Tool;
|
||||
|
||||
class Remove_Local_Files extends Background_Tool {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tool_key = 'remove_local_files';
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
protected static $requires_bucket_access = false;
|
||||
|
||||
/**
|
||||
* Should render.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_render() {
|
||||
if ( ! $this->as3cf->is_pro_plugin_setup() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $this->as3cf->get_setting( 'remove-local-file', false ) && $this->count_offloaded_media_files();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text() {
|
||||
return __( 'Remove all files from server', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more info text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_more_info_text() {
|
||||
return __( 'You can use this tool to delete all media files from your local server that have already been offloaded.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prompt text for when tool could be run in response to settings change.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_prompt_text() {
|
||||
global $as3cf;
|
||||
|
||||
$mesg = __( 'You\'ve enabled the "Remove Local Media" option. Do you want to remove all existing files from the server that have already been offloaded?', 'amazon-s3-and-cloudfront' );
|
||||
$mesg .= ' ';
|
||||
$mesg .= $as3cf::settings_more_info_link(
|
||||
'remove-local-file',
|
||||
'',
|
||||
'remove+local+files'
|
||||
);
|
||||
|
||||
return $mesg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_button_text() {
|
||||
return __( 'Begin Removal', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_queued_status(): string {
|
||||
return __( 'Removing media files from your local server.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_short_queued_status(): string {
|
||||
return _x( 'Removing local…', 'Short tool running message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Message for error notice
|
||||
*
|
||||
* @param string|null $message Optional message to override the default for the tool.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_error_notice_message( $message = null ) {
|
||||
$title = __( 'Remove From Local Errors', 'amazon-s3-and-cloudfront' );
|
||||
$message = empty( $message ) ? __( 'Previous attempts at removing your offloaded media library files from the server have resulted in errors.', 'amazon-s3-and-cloudfront' ) : $message;
|
||||
|
||||
return sprintf( '<strong>%s</strong> — %s', $title, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get background process class.
|
||||
*
|
||||
* @return Background_Tool_Process|null
|
||||
*/
|
||||
protected function get_background_process_class() {
|
||||
return new Remove_Local_Files_Process( $this->as3cf, $this );
|
||||
}
|
||||
}
|
||||
142
classes/pro/tools/update-acls.php
Normal file
142
classes/pro/tools/update-acls.php
Normal file
@@ -0,0 +1,142 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Tools;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Background_Tool_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Update_ACLs_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Tool;
|
||||
|
||||
class Update_ACLs extends Background_Tool {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tool_key = 'update_acls';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tab = 'media';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected static $show_tool_constants = array(
|
||||
'AS3CF_SHOW_UPDATE_ACLS_TOOL',
|
||||
);
|
||||
|
||||
/**
|
||||
* Should render.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_render() {
|
||||
if ( ! $this->as3cf->is_pro_plugin_setup() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( false !== static::show_tool_constant() && constant( static::show_tool_constant() ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return $this->is_active();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text() {
|
||||
return __( 'Update Object ACLs', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_button_text() {
|
||||
return __( 'Begin Update', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_queued_status(): string {
|
||||
return __( 'Updating object ACLs in bucket.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_short_queued_status(): string {
|
||||
return _x( 'Updating ACLs…', 'Short tool running message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more info text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_more_info_text() {
|
||||
return __( 'After disabling Block All Public Access for a bucket, you many need to update all the objects in the bucket to apply appropriate public and private ACLs so that they can be used correctly.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prompt text for when tool could be run in response to settings change.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_prompt_text() {
|
||||
global $as3cf;
|
||||
|
||||
$mesg = __( 'It looks like your configuration has changed to require public and private ACLs on objects, would you like to update the ACLs on your objects?', 'amazon-s3-and-cloudfront' );
|
||||
$mesg .= ' ';
|
||||
$mesg .= $as3cf::more_info_link(
|
||||
'/wp-offload-media/doc/block-all-public-access-to-bucket/',
|
||||
'update+acls',
|
||||
'disable-block-access'
|
||||
);
|
||||
|
||||
return $mesg;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function get_doc_url() {
|
||||
global $as3cf;
|
||||
|
||||
$args = array( 'utm_campaign' => 'update+acls' );
|
||||
|
||||
return $as3cf::dbrains_url( '/wp-offload-media/doc/block-all-public-access-to-bucket/', $args, 'disable-block-access' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Message for error notice.
|
||||
*
|
||||
* @param string|null $message Optional message to override the default for the tool.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_error_notice_message( $message = null ) {
|
||||
$title = __( 'Update ACLs Errors', 'amazon-s3-and-cloudfront' );
|
||||
$message = empty( $message ) ? __( 'Previous attempts at updating object ACLs have resulted in errors.', 'amazon-s3-and-cloudfront' ) : $message;
|
||||
|
||||
return sprintf( '<strong>%1$s</strong> — %2$s', $title, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get background process class.
|
||||
*
|
||||
* @return Background_Tool_Process|null
|
||||
*/
|
||||
protected function get_background_process_class() {
|
||||
return new Update_ACLs_Process( $this->as3cf, $this );
|
||||
}
|
||||
}
|
||||
262
classes/pro/tools/uploader.php
Normal file
262
classes/pro/tools/uploader.php
Normal file
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Tools;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Background_Tool_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Uploader_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Tool;
|
||||
use Exception;
|
||||
|
||||
class Uploader extends Background_Tool {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tool_key = 'uploader';
|
||||
|
||||
/**
|
||||
* Initialize Uploader
|
||||
*/
|
||||
public function init() {
|
||||
parent::init();
|
||||
|
||||
$this->error_setting_migration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate old upload errors to new setting key.
|
||||
*
|
||||
* TODO: Move this into next migration so it no longer fires
|
||||
* TODO: every time the plugin serves a request for tool state!
|
||||
*/
|
||||
protected function error_setting_migration() {
|
||||
$errors = $this->as3cf->get_setting( 'bulk_upload_errors', false );
|
||||
|
||||
if ( ! empty( $errors ) ) {
|
||||
$this->update_errors( $errors );
|
||||
$this->as3cf->remove_setting( 'bulk_upload_errors' );
|
||||
$this->as3cf->save_settings();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get info for tool, including current status.
|
||||
*
|
||||
* @return array
|
||||
* @throws Exception
|
||||
*/
|
||||
public function get_info() {
|
||||
$media_counts = $this->as3cf->media_counts();
|
||||
$human_percentage = $this->percent_offloaded();
|
||||
|
||||
$strings = array(
|
||||
'title_initial' => __( 'Your media needs to be offloaded', 'amazon-s3-and-cloudfront' ),
|
||||
'title_partial_complete' => __( '<strong>%s%%</strong> of your media has been offloaded', 'amazon-s3-and-cloudfront' ),
|
||||
'title_complete' => __( '<strong>100%</strong> of your media has been offloaded, congratulations!', 'amazon-s3-and-cloudfront' ),
|
||||
);
|
||||
|
||||
switch ( $human_percentage ) {
|
||||
case 0: // Entire library needs uploading
|
||||
$progress_description = $strings['title_initial'];
|
||||
break;
|
||||
|
||||
case 100: // Entire media library uploaded
|
||||
$progress_description = $strings['title_complete'];
|
||||
break;
|
||||
|
||||
default: // Media library upload partially complete
|
||||
$progress_description = sprintf( $strings['title_partial_complete'], $human_percentage );
|
||||
}
|
||||
|
||||
$args = array(
|
||||
'total_progress' => $human_percentage,
|
||||
'total_processed' => (int) $media_counts['offloaded'],
|
||||
'total_items' => (int) $media_counts['total'],
|
||||
'progress_description' => $progress_description,
|
||||
);
|
||||
|
||||
return array_merge( parent::get_info(), $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the percentage of media library items offloaded, to the nearest integer.
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function percent_offloaded() {
|
||||
static $human_percentage;
|
||||
|
||||
if ( is_null( $human_percentage ) ) {
|
||||
$media_counts = $this->as3cf->media_counts();
|
||||
|
||||
if ( empty( $media_counts['total'] ) || empty( $media_counts['offloaded'] ) ) {
|
||||
$human_percentage = 0;
|
||||
} else {
|
||||
$uploaded_percentage = (float) $media_counts['offloaded'] / $media_counts['total'];
|
||||
$human_percentage = (int) floor( $uploaded_percentage * 100 );
|
||||
|
||||
// Percentage of library needs uploading.
|
||||
if ( 0 === $human_percentage && $uploaded_percentage > 0 ) {
|
||||
$human_percentage = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $human_percentage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Should the tool be rendered?
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_render() {
|
||||
// Don't show tool if pro not set up.
|
||||
if ( ! $this->as3cf->is_pro_plugin_setup() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$media_counts = $this->as3cf->media_counts();
|
||||
|
||||
// Don't show upload tool if media library empty.
|
||||
if ( 0 === $media_counts['total'] ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Message for error notice
|
||||
*
|
||||
* @param string|null $message Optional message to override the default for the tool.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_error_notice_message( $message = null ) {
|
||||
$title = __( 'Offload Errors', 'amazon-s3-and-cloudfront' );
|
||||
$message = empty( $message ) ? __( 'Previous attempts at offloading your media library have resulted in errors.', 'amazon-s3-and-cloudfront' ) : $message;
|
||||
|
||||
return sprintf( '<strong>%s</strong> — %s', $title, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle start.
|
||||
*/
|
||||
public function handle_start() {
|
||||
$notice_id = $this->get_tool_key() . '_license_limit';
|
||||
$this->as3cf->notices->remove_notice_by_id( $notice_id );
|
||||
|
||||
parent::handle_start();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tool's name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return __( 'Offload Media', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text() {
|
||||
return __( 'Your media needs to be offloaded', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title text for when tool is partially complete.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text_partial_complete() {
|
||||
return __( 'Offload Media', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title text for when tool is complete.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text_complete() {
|
||||
return __( 'Offload Media', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more info text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_more_info_text() {
|
||||
return __( 'This tool goes through all your media items and offloads their files to the bucket.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get prompt text for when tool could be run in response to settings change.
|
||||
* Defaults to more info text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_prompt_text() {
|
||||
global $as3cf;
|
||||
|
||||
$mesg = __( 'You\'ve enabled the "Offload Media" option. Do you want to copy all yet to be offloaded media files to the bucket?', 'amazon-s3-and-cloudfront' );
|
||||
$mesg .= ' ';
|
||||
$mesg .= $as3cf::settings_more_info_link(
|
||||
'copy-to-s3',
|
||||
'',
|
||||
'copy+to+s3'
|
||||
);
|
||||
|
||||
return $mesg;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_button_text() {
|
||||
return __( 'Offload Now', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button text for when tool is partially complete.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_button_text_partial_complete() {
|
||||
return __( 'Offload Remaining Now', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_queued_status(): string {
|
||||
return __( 'Offloading media items to bucket.', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_short_queued_status(): string {
|
||||
return _x( 'Offloading…', 'Short tool running message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get background process class.
|
||||
*
|
||||
* @return Background_Tool_Process|null
|
||||
*/
|
||||
protected function get_background_process_class() {
|
||||
return new Uploader_Process( $this->as3cf, $this );
|
||||
}
|
||||
}
|
||||
131
classes/pro/tools/woocommerce-product-urls.php
Normal file
131
classes/pro/tools/woocommerce-product-urls.php
Normal file
@@ -0,0 +1,131 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Tools;
|
||||
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Background_Tool_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Processes\Woocommerce_Product_Urls_Process;
|
||||
use DeliciousBrains\WP_Offload_Media\Pro\Background_Tool;
|
||||
|
||||
class Woocommerce_Product_Urls extends Background_Tool {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $tool_key = 'woocommerce_product_urls';
|
||||
|
||||
/**
|
||||
* Limit the item types that this tool handles.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $source_types = array(
|
||||
'media-library',
|
||||
);
|
||||
|
||||
/**
|
||||
* Message for error notice
|
||||
*
|
||||
* @param string|null $message Optional message to override the default for the tool.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function get_error_notice_message( $message = null ) {
|
||||
$title = __( 'WooCommerce verify and update errors', 'amazon-s3-and-cloudfront' );
|
||||
$message = empty( $message ) ? __( 'Previous attempts at verifying and updating WooCommerce products have resulted in errors.', 'amazon-s3-and-cloudfront' ) : $message;
|
||||
|
||||
return sprintf( '<strong>%s</strong> — %s', $title, $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Should render.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function should_render() {
|
||||
if ( ! $this->as3cf->is_pro_plugin_setup() ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( defined( 'AS3CF_SHOW_WOOCOMMERCE_TOOL' ) && AS3CF_SHOW_WOOCOMMERCE_TOOL ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ( defined( 'AS3CF_SHOW_WOOCOMMERCE_TOOL' ) && ! AS3CF_SHOW_WOOCOMMERCE_TOOL ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( ! class_exists( 'WooCommerce' ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (bool) $this->count_offloaded_media_files();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the tool's name.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_name() {
|
||||
return __( 'WooCommerce Analyze & Repair', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get title text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_title_text() {
|
||||
return __( 'Verify and update WooCommerce downloadable products', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get more info text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_more_info_text() {
|
||||
return __(
|
||||
'This tools goes through all your WooCommerce products with downloadable files. It marks all
|
||||
downloadable files as private in the bucket. It also replaces shortcodes created by previous versions
|
||||
of this plugin with proper URLs.',
|
||||
'amazon-s3-and-cloudfront'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get button text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_button_text() {
|
||||
return __( 'Verify and update', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_queued_status(): string {
|
||||
return __( 'Verify and update WooCommerce downloadable files', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get short queued status text.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function get_short_queued_status(): string {
|
||||
return _x( 'Repairing…', 'Short tool running message', 'amazon-s3-and-cloudfront' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get background process class.
|
||||
*
|
||||
* @return Background_Tool_Process|null
|
||||
*/
|
||||
protected function get_background_process_class() {
|
||||
return new Woocommerce_Product_Urls_Process( $this->as3cf, $this );
|
||||
}
|
||||
}
|
||||
262
classes/pro/upgrades/disable-compatibility-plugins.php
Normal file
262
classes/pro/upgrades/disable-compatibility-plugins.php
Normal file
@@ -0,0 +1,262 @@
|
||||
<?php
|
||||
|
||||
namespace DeliciousBrains\WP_Offload_Media\Pro\Upgrades;
|
||||
|
||||
use Amazon_S3_And_CloudFront;
|
||||
use DeliciousBrains\WP_Offload_Media\Upgrades\Network_Upgrade;
|
||||
|
||||
class Disable_Compatibility_Plugins extends Network_Upgrade {
|
||||
/**
|
||||
* Network_Upgrade constructor.
|
||||
*
|
||||
* @param Amazon_S3_And_CloudFront $as3cf
|
||||
* @param string $version
|
||||
*/
|
||||
public function __construct( $as3cf, $version ) {
|
||||
parent::__construct( $as3cf, $version );
|
||||
|
||||
add_action( 'as3cf_init', array( $this, 'disable_obsolete_plugins' ), 5 );
|
||||
add_action( 'as3cf_pre_settings_render', array( $this, 'show_obsolete_notice' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform upgrade logic.
|
||||
*/
|
||||
protected function do_upgrade() {
|
||||
$this->remove_existing_notice();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove existing compatibility notice.
|
||||
*/
|
||||
protected function remove_existing_notice() {
|
||||
$notice_id = 'as3cf-compat-addons';
|
||||
|
||||
if ( $this->as3cf->notices->find_notice_by_id( $notice_id ) ) {
|
||||
$this->as3cf->notices->undismiss_notice_for_all( $notice_id );
|
||||
$this->as3cf->notices->remove_notice_by_id( $notice_id );
|
||||
}
|
||||
|
||||
delete_site_option( 'as3cf_compat_addons_to_install' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Show deactivation notice.
|
||||
*/
|
||||
protected function show_deactivation_notice() {
|
||||
$active_plugins = $this->get_active_plugins();
|
||||
|
||||
if ( empty( $active_plugins ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$id = 'disable-compat-plugins';
|
||||
$plugins = $this->render_plugins( $active_plugins );
|
||||
$args = array(
|
||||
'type' => 'notice-info',
|
||||
'custom_id' => $id,
|
||||
'only_show_to_user' => false,
|
||||
'flash' => false,
|
||||
);
|
||||
|
||||
if ( $this->as3cf->notices->find_notice_by_id( $id ) ) {
|
||||
$this->as3cf->notices->undismiss_notice_for_all( $id );
|
||||
$this->as3cf->notices->remove_notice_by_id( $id );
|
||||
}
|
||||
|
||||
$this->as3cf->notices->add_notice( $this->render_deactivation_notice( $plugins ), $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render deactivation notice.
|
||||
*
|
||||
* @param string $plugins
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function render_deactivation_notice( string $plugins ): string {
|
||||
$message = __( "We've deactivated the following WP Offload Media addons:", 'amazon-s3-and-cloudfront' );
|
||||
$more_info = __( 'Integrations are now included in the core WP Offload Media plugin. The addon plugins listed above can safely be removed.', 'amazon-s3-and-cloudfront' );
|
||||
|
||||
return $message . $plugins . $more_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render plugins list.
|
||||
*
|
||||
* @param array $plugins
|
||||
* @param bool $uninstall
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function render_plugins( array $plugins, bool $uninstall = false ): string {
|
||||
$html = '<ul style="list-style-type: disc; padding: 0 0 0 30px; margin: 5px 0;">';
|
||||
|
||||
foreach ( $plugins as $plugin => $details ) {
|
||||
$html .= '<li style="margin: 0;">' . $details['name'];
|
||||
|
||||
if ( $uninstall ) {
|
||||
$html .= ' (<a href="' . wp_nonce_url( 'plugins.php?action=delete-selected&checked[]=' . $plugin, 'bulk-plugins' ) . '">';
|
||||
$html .= _x( 'Remove', 'Remove plugin', 'amazon-s3-and-cloudfront' );
|
||||
$html .= '</a>)';
|
||||
}
|
||||
|
||||
$html .= '</li>';
|
||||
}
|
||||
|
||||
$html .= '</ul>';
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get compatibility plugins.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_plugins(): array {
|
||||
return array(
|
||||
'amazon-s3-and-cloudfront-acf-image-crop/amazon-s3-and-cloudfront-acf-image-crop.php' => array(
|
||||
'name' => __( 'ACF Image Crop', 'amazon-s3-and-cloudfront' ),
|
||||
'init_action' => 'aws_init',
|
||||
'init_func' => 'as3cf_acf_image_crop_init',
|
||||
),
|
||||
'amazon-s3-and-cloudfront-edd/amazon-s3-and-cloudfront-edd.php' => array(
|
||||
'name' => __( 'Easy Digital Downloads', 'amazon-s3-and-cloudfront' ),
|
||||
'init_action' => 'aws_init',
|
||||
'init_func' => 'as3cf_edd_init',
|
||||
),
|
||||
'amazon-s3-and-cloudfront-enable-media-replace/amazon-s3-and-cloudfront-enable-media-replace.php' => array(
|
||||
'name' => __( 'Enable Media Replace', 'amazon-s3-and-cloudfront' ),
|
||||
'init_action' => 'aws_init',
|
||||
'init_func' => 'as3cf_enable_media_replace_init',
|
||||
),
|
||||
'amazon-s3-and-cloudfront-meta-slider/amazon-s3-and-cloudfront-meta-slider.php' => array(
|
||||
'name' => __( 'Meta Slider', 'amazon-s3-and-cloudfront' ),
|
||||
'init_action' => 'aws_init',
|
||||
'init_func' => 'as3cf_meta_slider_init',
|
||||
),
|
||||
'amazon-s3-and-cloudfront-woocommerce/amazon-s3-and-cloudfront-woocommerce.php' => array(
|
||||
'name' => __( 'WooCommerce', 'amazon-s3-and-cloudfront' ),
|
||||
'init_action' => 'aws_init',
|
||||
'init_func' => 'as3cf_woocommerce_init',
|
||||
),
|
||||
'amazon-s3-and-cloudfront-wpml/amazon-s3-and-cloudfront-wpml.php' => array(
|
||||
'name' => __( 'WPML', 'amazon-s3-and-cloudfront' ),
|
||||
'init_action' => 'aws_init',
|
||||
'init_func' => 'as3cf_wpml_init',
|
||||
),
|
||||
'amazon-s3-and-cloudfront-assets/amazon-s3-and-cloudfront-assets.php' => array(
|
||||
'name' => __( 'Assets', 'amazon-s3-and-cloudfront' ),
|
||||
'init_action' => 'as3cf_pro_init',
|
||||
'init_func' => 'as3cf_assets_init',
|
||||
),
|
||||
'amazon-s3-and-cloudfront-assets-pull/amazon-s3-and-cloudfront-assets-pull.php' => array(
|
||||
'name' => __( 'Assets Pull', 'amazon-s3-and-cloudfront' ),
|
||||
'init_action' => 'as3cf_pro_init',
|
||||
'init_func' => 'as3cf_assets_pull_init',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active plugins.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_active_plugins(): array {
|
||||
static $active_plugins;
|
||||
|
||||
if ( is_null( $active_plugins ) ) {
|
||||
$active_plugins = array();
|
||||
|
||||
foreach ( $this->get_plugins() as $plugin => $details ) {
|
||||
if ( is_plugin_active( $plugin ) ) {
|
||||
$active_plugins[ $plugin ] = $details;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $active_plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get installed plugins.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
protected function get_installed_plugins(): array {
|
||||
$plugins = get_plugins();
|
||||
$installed_plugins = array();
|
||||
|
||||
foreach ( $this->get_plugins() as $plugin => $details ) {
|
||||
if ( array_key_exists( $plugin, $plugins ) ) {
|
||||
$installed_plugins[ $plugin ] = $details;
|
||||
}
|
||||
}
|
||||
|
||||
return $installed_plugins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Show obsolete notice.
|
||||
*/
|
||||
public function show_obsolete_notice() {
|
||||
$id = 'remove-compat-plugins';
|
||||
$installed_plugins = $this->get_installed_plugins();
|
||||
|
||||
if ( empty( $installed_plugins ) ) {
|
||||
if ( $this->as3cf->notices->find_notice_by_id( $id ) ) {
|
||||
$this->as3cf->notices->remove_notice_by_id( $id );
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$plugins = $this->render_plugins( $installed_plugins, true );
|
||||
$args = array(
|
||||
'type' => 'notice-info',
|
||||
'dismissible' => false,
|
||||
'flash' => false,
|
||||
'only_show_to_user' => false,
|
||||
'only_show_in_settings' => true,
|
||||
'custom_id' => $id,
|
||||
);
|
||||
|
||||
$this->as3cf->notices->add_notice( $this->render_obsolete_notice( $plugins ), $args );
|
||||
}
|
||||
|
||||
/**
|
||||
* Render obsolete notice.
|
||||
*
|
||||
* @param string $plugins
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function render_obsolete_notice( string $plugins ): string {
|
||||
$message = __( 'Please remove the following obsolete addons:', 'amazon-s3-and-cloudfront' );
|
||||
$more_info = __( 'Integrations are now included in the core WP Offload Media plugin. Once these addons are removed this message will go away.', 'amazon-s3-and-cloudfront' );
|
||||
|
||||
return $message . $plugins . $more_info;
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable obsolete plugins.
|
||||
*/
|
||||
public function disable_obsolete_plugins() {
|
||||
$this->show_deactivation_notice();
|
||||
|
||||
$plugins = $this->get_active_plugins();
|
||||
|
||||
if ( ! empty( $plugins ) ) {
|
||||
foreach ( $plugins as $details ) {
|
||||
$priority = has_action( $details['init_action'], $details['init_func'] );
|
||||
|
||||
if ( $priority ) {
|
||||
remove_action( $details['init_action'], $details['init_func'], $priority );
|
||||
}
|
||||
}
|
||||
deactivate_plugins( array_keys( $plugins ), true );
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user