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:
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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user