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