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
1532 lines
46 KiB
PHP
1532 lines
46 KiB
PHP
<?php
|
|
|
|
namespace DeliciousBrains\WP_Offload_Media\Pro\Integrations;
|
|
|
|
use Amazon_S3_And_CloudFront_Pro;
|
|
use AS3CF_Error;
|
|
use AS3CF_Utils;
|
|
use DeliciousBrains\WP_Offload_Media\Integrations\Media_Library;
|
|
use DeliciousBrains\WP_Offload_Media\Items\Download_Handler;
|
|
use DeliciousBrains\WP_Offload_Media\Items\Item;
|
|
use DeliciousBrains\WP_Offload_Media\Items\Media_Library_Item;
|
|
use DeliciousBrains\WP_Offload_Media\Items\Remove_Local_Handler;
|
|
use DeliciousBrains\WP_Offload_Media\Items\Upload_Handler;
|
|
use DeliciousBrains\WP_Offload_Media\Pro\Items\Remove_Provider_Handler;
|
|
use DeliciousBrains\WP_Offload_Media\Pro\Items\Update_Acl_Handler;
|
|
use DeliciousBrains\WP_Offload_Media\Providers\Provider;
|
|
use Exception;
|
|
use WP_Error;
|
|
use WP_Post;
|
|
use WP_Query;
|
|
use WP_User;
|
|
|
|
class Media_Library_Pro extends Media_Library {
|
|
/**
|
|
* @var array
|
|
*/
|
|
protected $messages;
|
|
/**
|
|
* @var array
|
|
*/
|
|
protected $user_can_use_media_actions;
|
|
|
|
/**
|
|
* @var Amazon_S3_And_CloudFront_Pro
|
|
*/
|
|
protected $as3cf;
|
|
|
|
/**
|
|
* Keep track of media library columns managed by us.
|
|
*
|
|
* @var array
|
|
*/
|
|
private $managed_columns = array();
|
|
|
|
/**
|
|
* Available media actions for the bucket column.
|
|
*
|
|
* @var array
|
|
*/
|
|
private $bucket_col_actions = array();
|
|
|
|
/**
|
|
* Available media actions for the access column.
|
|
*
|
|
* @var array
|
|
*/
|
|
private $access_col_actions = array();
|
|
|
|
/**
|
|
* Config for filters rendered in list and grid views
|
|
*
|
|
* @var array
|
|
*/
|
|
private $media_library_filters = array();
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
public function setup() {
|
|
parent::setup();
|
|
|
|
add_action( 'load-upload.php', array( $this, 'load_media_pro_assets' ), 11 );
|
|
add_action( 'as3cf_load_attachment_assets', array( $this, 'load_attachment_js' ) );
|
|
|
|
// Ajax handlers
|
|
add_action( 'wp_ajax_as3cfpro_process_media_action', array( $this, 'ajax_process_media_action' ) );
|
|
add_action( 'wp_ajax_as3cfpro_update_acl', array( $this, 'ajax_update_acl' ) );
|
|
|
|
// Pro customisations
|
|
add_filter( 'as3cf_media_action_strings', array( $this, 'media_action_strings' ) );
|
|
|
|
if ( ! is_multisite() ) {
|
|
add_filter( 'as3cf_get_summary_counts', array( $this, 'add_urls_to_summary_counts' ) );
|
|
}
|
|
|
|
// Media row actions
|
|
add_filter( 'wp_prepare_attachment_for_js', array( $this, 'enrich_attachment_model' ), 10, 2 );
|
|
add_filter( 'bulk_actions-upload', array( $this, 'add_list_table_bulk_actions' ) );
|
|
add_filter( 'media_row_actions', array( $this, 'add_media_row_actions' ), 10, 2 );
|
|
add_action( 'admin_notices', array( $this, 'maybe_display_media_action_message' ) );
|
|
add_action( 'admin_init', array( $this, 'process_media_actions' ) );
|
|
|
|
// Columns and filters
|
|
if ( is_admin() ) {
|
|
add_filter( 'manage_media_columns', array( $this, 'add_media_library_columns' ) );
|
|
add_filter( 'manage_upload_sortable_columns', array( $this, 'add_media_library_sortable_columns' ) );
|
|
add_filter( 'manage_media_custom_column', array( $this, 'get_media_library_column_content' ), 10, 2 );
|
|
add_action( 'restrict_manage_posts', array( $this, 'add_media_library_filters' ) );
|
|
add_action( 'pre_get_posts', array( $this, 'media_library_filter_query' ) );
|
|
|
|
$this->managed_columns = array(
|
|
'as3cf_bucket' => _x( 'Bucket', 'Media Library column heading', 'amazon-s3-and-cloudfront' ),
|
|
'as3cf_access' => _x( 'Access', 'Media Library column heading', 'amazon-s3-and-cloudfront' ),
|
|
);
|
|
}
|
|
|
|
// Update media counts cache with current provider and bucket usage.
|
|
add_filter( 'as3cf_media_counts_for_blog', array( $this, 'add_providers_and_buckets_to_media_counts' ), 10, 3 );
|
|
}
|
|
|
|
/**
|
|
* Load the media Pro assets
|
|
*/
|
|
public function load_media_pro_assets() {
|
|
if ( ! $this->as3cf->is_plugin_setup() ) {
|
|
return;
|
|
}
|
|
|
|
$this->as3cf->enqueue_script( 'as3cf-pro-media-script', 'assets/js/pro/media', array(
|
|
'jquery',
|
|
'media-views',
|
|
'media-grid',
|
|
'wp-util',
|
|
), false );
|
|
|
|
$nonces = array(
|
|
'get_attachment_provider_details' => wp_create_nonce( 'get-attachment-s3-details' ),
|
|
);
|
|
|
|
foreach ( $this->get_available_media_actions() as $action => $scopes ) {
|
|
foreach ( $scopes as $scope ) {
|
|
$nonces[ $scope . '_' . $action ] = wp_create_nonce( "$scope-$action" );
|
|
}
|
|
}
|
|
|
|
wp_localize_script( 'as3cf-pro-media-script', 'as3cfpro_media', array(
|
|
'strings' => $this->get_media_action_strings(),
|
|
'actions' => array(
|
|
'bulk' => $this->get_available_media_actions( 'bulk' ),
|
|
'singular' => $this->get_available_media_actions( 'singular' ),
|
|
),
|
|
'nonces' => $nonces,
|
|
'settings' => array(
|
|
'default_acl' => $this->as3cf->get_storage_provider()->get_default_acl(),
|
|
'private_acl' => $this->as3cf->get_storage_provider()->get_private_acl(),
|
|
),
|
|
'filters' => $this->get_media_library_filters_config(),
|
|
) );
|
|
}
|
|
|
|
/**
|
|
* Load the attachment JS only when editing an attachment.
|
|
*/
|
|
public function load_attachment_js() {
|
|
$this->as3cf->enqueue_script( 'as3cf-pro-attachment-script', 'assets/js/pro/attachment', array(
|
|
'jquery',
|
|
'wp-util',
|
|
), false );
|
|
|
|
$actions = $this->get_available_media_actions( 'singular' );
|
|
$nonces = array();
|
|
|
|
foreach ( $actions as $action ) {
|
|
$nonces[ 'singular_' . $action ] = wp_create_nonce( "singular-$action" );
|
|
}
|
|
|
|
wp_localize_script( 'as3cf-pro-attachment-script', 'as3cfpro_media', array(
|
|
'strings' => array(
|
|
'local_warning' => $this->get_media_action_strings( 'local_warning' ),
|
|
'updating_acl' => $this->get_media_action_strings( 'updating_acl' ),
|
|
'change_acl_error' => $this->get_media_action_strings( 'change_acl_error' ),
|
|
),
|
|
'actions' => $actions,
|
|
'nonces' => $nonces,
|
|
'settings' => array(
|
|
'post_id' => get_the_ID(),
|
|
'default_acl' => $this->as3cf->get_storage_provider()->get_default_acl(),
|
|
'private_acl' => $this->as3cf->get_storage_provider()->get_private_acl(),
|
|
),
|
|
) );
|
|
}
|
|
|
|
/**
|
|
* Handle updating the ACL for an attachment
|
|
*/
|
|
public function ajax_update_acl() {
|
|
check_ajax_referer( 'singular-update_acl' );
|
|
|
|
$id = $this->as3cf->filter_input( 'id', INPUT_POST, FILTER_VALIDATE_INT ); // input var ok
|
|
$acl = sanitize_text_field( $_REQUEST['acl'] ); // input var ok
|
|
$title = $this->get_media_action_strings( 'change_to_public' );
|
|
$is_private = true;
|
|
|
|
if ( empty( $id ) || empty( $acl ) ) {
|
|
wp_send_json_error();
|
|
}
|
|
|
|
if ( $this->as3cf->get_storage_provider()->get_private_acl() !== $acl ) {
|
|
$acl = $this->as3cf->get_storage_provider()->get_default_acl();
|
|
$title = $this->get_media_action_strings( 'change_to_private' );
|
|
$is_private = false;
|
|
}
|
|
|
|
// Update on provider.
|
|
$as3cf_item = Media_Library_Item::get_by_source_id( $id );
|
|
/** @var Update_Acl_Handler $updace_acl_handler */
|
|
$update_acl_handler = $this->as3cf->get_item_handler( Update_Acl_Handler::get_item_handler_key_name() );
|
|
$update = $update_acl_handler->handle( $as3cf_item, array( 'set_private' => $is_private ) );
|
|
|
|
$data = array(
|
|
'acl' => $acl,
|
|
'acl_display' => $this->as3cf->get_acl_display_name( $acl ),
|
|
'title' => $title,
|
|
'url' => wp_get_attachment_url( $id ),
|
|
);
|
|
|
|
if ( is_wp_error( $update ) ) {
|
|
wp_send_json_error();
|
|
}
|
|
|
|
wp_send_json_success( $data );
|
|
}
|
|
|
|
/**
|
|
* Enrich the attachment model attributes used in JS
|
|
*
|
|
* @param array $response Array of prepared attachment data.
|
|
* @param int|object $attachment Attachment ID or object.
|
|
*
|
|
* @return array
|
|
*/
|
|
public function enrich_attachment_model( $response, $attachment ) {
|
|
$file = get_attached_file( $attachment->ID, true );
|
|
|
|
// flag if the attachment file doesn't exist locally
|
|
// so we can ask for confirmation when removing from S3
|
|
$response['bulk_local_warning'] = ! file_exists( $file );
|
|
|
|
return $response;
|
|
}
|
|
|
|
/**
|
|
* Add bulk media actions to a list table's bulk actions dropdown.
|
|
*
|
|
* @param array $actions
|
|
*
|
|
* @return array
|
|
*/
|
|
public function add_list_table_bulk_actions( $actions ) {
|
|
$strings = $this->get_media_action_strings();
|
|
|
|
foreach ( $this->get_available_media_actions( 'bulk' ) as $action ) {
|
|
$actions[ 'bulk_as3cfpro_' . $action ] = $strings[ $action ];
|
|
}
|
|
|
|
return $actions;
|
|
}
|
|
|
|
/**
|
|
* Add Pro media action strings.
|
|
*
|
|
* @param array $strings
|
|
*
|
|
* @return array
|
|
*/
|
|
public function media_action_strings( $strings ) {
|
|
$strings['copy'] = __( 'Copy to Bucket', 'amazon-s3-and-cloudfront' );
|
|
$strings['remove'] = __( 'Remove from Bucket', 'amazon-s3-and-cloudfront' );
|
|
$strings['remove_local'] = __( 'Remove from Server', 'amazon-s3-and-cloudfront' );
|
|
$strings['download'] = __( 'Copy from Bucket to Server', 'amazon-s3-and-cloudfront' );
|
|
$strings['private_acl'] = __( 'Make Private in Bucket', 'amazon-s3-and-cloudfront' );
|
|
$strings['public_acl'] = __( 'Make Public in Bucket', 'amazon-s3-and-cloudfront' );
|
|
$strings['local_warning'] = __( 'This file does not exist locally so removing it from the bucket will result in broken links on your site. Are you sure you want to continue?', 'amazon-s3-and-cloudfront' );
|
|
$strings['bulk_local_warning'] = __( 'Some files do not exist locally so removing them from the bucket will result in broken links on your site. Are you sure you want to continue?', 'amazon-s3-and-cloudfront' );
|
|
$strings['change_to_private'] = __( 'Click to set as Private in the bucket', 'amazon-s3-and-cloudfront' );
|
|
$strings['change_to_public'] = __( 'Click to set as Public in the bucket', 'amazon-s3-and-cloudfront' );
|
|
$strings['updating_acl'] = __( 'Updating…', 'amazon-s3-and-cloudfront' );
|
|
$strings['change_acl_error'] = __( 'There was an error changing the ACL. Make sure the IAM user has permission to change the ACL and try again.', 'amazon-s3-and-cloudfront' );
|
|
|
|
return $strings;
|
|
}
|
|
|
|
/**
|
|
* Conditionally adds media action links for an attachment on the Media library list view.
|
|
*
|
|
* @param array $actions
|
|
* @param WP_Post|int $post
|
|
*
|
|
* @return array
|
|
*/
|
|
public function add_media_row_actions( array $actions, $post ) {
|
|
$available_actions = $this->get_available_media_actions( 'singular' );
|
|
|
|
$this->bucket_col_actions = array();
|
|
$this->access_col_actions = array();
|
|
|
|
if ( ! $available_actions ) {
|
|
return $actions;
|
|
}
|
|
|
|
$post_id = ( is_object( $post ) ) ? $post->ID : $post;
|
|
$file = get_attached_file( $post_id, true );
|
|
$file_exists = file_exists( $file );
|
|
$as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
|
|
|
|
// If offloaded to another provider can not do anything.
|
|
if ( $as3cf_item && ! $as3cf_item->served_by_provider( true ) ) {
|
|
$this->bucket_col_actions['as3cfpro_wrong_provider'] = sprintf(
|
|
'<span title="%s" class="%s">%s</span>',
|
|
__( 'Offloaded to a different provider than currently configured.', 'amazon-s3-and-cloudfront' ),
|
|
'as3cf-warning',
|
|
__( 'Disconnected', 'amazon-s3-and-cloudfront' )
|
|
);
|
|
|
|
return $actions;
|
|
}
|
|
|
|
// If not offloaded at all, or offloaded to current provider, can use copy.
|
|
if ( in_array( 'copy', $available_actions ) && $file_exists && ( ! $as3cf_item || $as3cf_item->served_by_provider( true ) ) ) {
|
|
$this->add_media_row_action( $actions, $post_id, 'copy' );
|
|
}
|
|
|
|
// Actions beyond this point are for items on provider only
|
|
if ( ! $as3cf_item || ! $as3cf_item->served_by_provider( true ) ) {
|
|
return $actions;
|
|
}
|
|
|
|
if ( in_array( 'remove', $available_actions ) ) {
|
|
if ( 'grid' === $this->render_context ) {
|
|
$this->add_media_row_action( $actions, $post_id, 'remove' );
|
|
} else {
|
|
$this->add_media_row_action( $this->bucket_col_actions, $post_id, 'remove' );
|
|
}
|
|
}
|
|
|
|
if ( in_array( 'download', $available_actions ) && ! $file_exists ) {
|
|
$this->add_media_row_action( $actions, $post_id, 'download' );
|
|
}
|
|
|
|
if ( in_array( 'private_acl', $available_actions ) && ! $as3cf_item->is_private() ) {
|
|
if ( 'grid' === $this->render_context ) {
|
|
$this->add_media_row_action( $actions, $post_id, 'private_acl' );
|
|
} else {
|
|
$this->add_media_row_action( $this->access_col_actions, $post_id, 'private_acl', __( 'Make Private', 'amazon-s3-and-cloudfront' ) );
|
|
}
|
|
}
|
|
|
|
if ( in_array( 'public_acl', $available_actions ) && $as3cf_item->is_private() ) {
|
|
if ( 'grid' === $this->render_context ) {
|
|
$this->add_media_row_action( $actions, $post_id, 'public_acl' );
|
|
} else {
|
|
$this->add_media_row_action( $this->access_col_actions, $post_id, 'public_acl', __( 'Make Public', 'amazon-s3-and-cloudfront' ) );
|
|
}
|
|
}
|
|
|
|
if ( in_array( 'remove_local', $available_actions ) && $file_exists ) {
|
|
$this->add_media_row_action( $actions, $post_id, 'remove_local' );
|
|
}
|
|
|
|
return $actions;
|
|
}
|
|
|
|
/**
|
|
* Display notices after processing media actions
|
|
*/
|
|
public function maybe_display_media_action_message() {
|
|
global $pagenow;
|
|
if ( ! in_array( $pagenow, array( 'upload.php', 'post.php' ) ) ) {
|
|
return;
|
|
}
|
|
|
|
if ( isset( $_GET['as3cfpro-action'] ) && isset( $_GET['errors'] ) && isset( $_GET['count'] ) ) {
|
|
$action = sanitize_key( $_GET['as3cfpro-action'] ); // input var okay
|
|
|
|
$error_count = absint( $_GET['errors'] ); // input var okay
|
|
$count = absint( $_GET['count'] ); // input var okay
|
|
|
|
$message_html = $this->get_media_action_result_message( $action, $count, $error_count );
|
|
|
|
if ( false !== $message_html ) {
|
|
echo $message_html;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handler for single and bulk media actions
|
|
*/
|
|
public function process_media_actions() {
|
|
if ( AS3CF_Utils::is_ajax() ) {
|
|
return;
|
|
}
|
|
|
|
global $pagenow;
|
|
if ( 'upload.php' != $pagenow ) {
|
|
return;
|
|
}
|
|
|
|
if ( ! isset( $_GET['action'] ) ) { // input var okay
|
|
return;
|
|
}
|
|
|
|
if ( ! empty( $_REQUEST['action2'] ) && '-1' != $_REQUEST['action2'] ) {
|
|
// Handle bulk actions from the footer bulk action select
|
|
$action = sanitize_key( $_REQUEST['action2'] ); // input var okay
|
|
} else {
|
|
$action = sanitize_key( $_REQUEST['action'] ); // input var okay
|
|
}
|
|
|
|
if ( false === strpos( $action, 'bulk_as3cfpro_' ) ) {
|
|
$available_actions = $this->get_available_media_actions( 'singular' );
|
|
$referrer = 'as3cfpro-' . $action;
|
|
$doing_bulk_action = false;
|
|
if ( ! isset( $_GET['ids'] ) ) {
|
|
return;
|
|
}
|
|
$ids = explode( ',', $_GET['ids'] ); // input var okay
|
|
} else {
|
|
$available_actions = $this->get_available_media_actions( 'bulk' );
|
|
$action = str_replace( 'bulk_as3cfpro_', '', $action );
|
|
$referrer = 'bulk-media';
|
|
$doing_bulk_action = true;
|
|
if ( ! isset( $_REQUEST['media'] ) ) {
|
|
return;
|
|
}
|
|
$ids = $_REQUEST['media']; // input var okay
|
|
}
|
|
|
|
if ( ! in_array( $action, $available_actions ) ) {
|
|
return;
|
|
}
|
|
|
|
$ids = array_map( 'intval', $ids );
|
|
$id_count = count( $ids );
|
|
|
|
check_admin_referer( $referrer );
|
|
|
|
$sendback = isset( $_GET['sendback'] ) ? $_GET['sendback'] : wp_get_referer();
|
|
|
|
$args = array(
|
|
'as3cfpro-action' => $action,
|
|
);
|
|
|
|
$result = $this->maybe_do_provider_action( $action, $ids, $doing_bulk_action );
|
|
|
|
if ( ! $result ) {
|
|
unset( $args['as3cfpro-action'] );
|
|
$result = array();
|
|
}
|
|
|
|
// If we're uploading a single file, add the id to the `$args` array.
|
|
if ( 'copy' === $action && 1 === $id_count && ! empty( $result ) && 1 === ( $result['count'] + $result['errors'] ) ) {
|
|
$args['as3cf_id'] = array_shift( $ids );
|
|
}
|
|
|
|
$args = array_merge( $args, $result );
|
|
$url = add_query_arg( $args, $sendback );
|
|
|
|
wp_redirect( esc_url_raw( $url ) );
|
|
$this->_exit();
|
|
}
|
|
|
|
/**
|
|
* Add an action link to the media actions array
|
|
*
|
|
* @param array $actions
|
|
* @param int $post_id
|
|
* @param string $action
|
|
* @param string $text
|
|
* @param bool $show_warning
|
|
*/
|
|
public function add_media_row_action( &$actions, $post_id, $action, $text = '', $show_warning = false ) {
|
|
$url = $this->get_media_action_url( $action, $post_id );
|
|
$text = $text ? $text : $this->get_media_action_strings( $action );
|
|
$class = $action;
|
|
if ( $show_warning ) {
|
|
$class .= ' local-warning';
|
|
}
|
|
|
|
$actions[ 'as3cfpro_' . $action ] = '<a href="' . $url . '" class="' . $class . '" title="' . esc_attr( $text ) . '">' . esc_html( $text ) . '</a>';
|
|
}
|
|
|
|
/**
|
|
* Check we can do the media actions
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function verify_media_actions() {
|
|
if ( ! $this->as3cf->is_pro_plugin_setup( true ) ) {
|
|
return false;
|
|
}
|
|
|
|
return $this->user_can_use_media_actions();
|
|
}
|
|
|
|
/**
|
|
* Get a list of available media actions which can be performed according to plugin and user capability requirements.
|
|
*
|
|
* @param string|null $scope
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_available_media_actions( $scope = null ) {
|
|
$actions = array();
|
|
|
|
if ( ! $this->as3cf->is_plugin_setup( true ) || ! $this->user_can_use_media_actions() ) {
|
|
return $actions;
|
|
}
|
|
|
|
// We've already tested provider credentials, but is license ok?
|
|
if ( $this->as3cf->is_pro_plugin_setup( true ) ) {
|
|
$actions['copy'] = array( 'singular', 'bulk' );
|
|
$actions['download'] = array( 'singular', 'bulk' );
|
|
$actions['update_acl'] = array( 'singular' );
|
|
$actions['private_acl'] = array( 'singular', 'bulk' );
|
|
$actions['public_acl'] = array( 'singular', 'bulk' );
|
|
$actions['remove_local'] = array( 'singular', 'bulk' );
|
|
}
|
|
|
|
// Remove from Bucket should still be available even if license exceeded/invalid.
|
|
$actions['remove'] = array( 'singular', 'bulk' );
|
|
|
|
if ( $scope ) {
|
|
$in_scope = array_filter( $actions, function ( $scopes ) use ( $scope ) {
|
|
return in_array( $scope, $scopes );
|
|
} );
|
|
|
|
return array_keys( $in_scope );
|
|
}
|
|
|
|
return $actions;
|
|
}
|
|
|
|
/**
|
|
* Check if the given user can use on-demand S3 media actions.
|
|
*
|
|
* @param null|int|WP_User $user User to check. Defaults to current user.
|
|
*
|
|
* @return bool
|
|
*/
|
|
public function user_can_use_media_actions( $user = null ) {
|
|
$user = $user ? $user : wp_get_current_user();
|
|
|
|
if ( is_object( $user ) ) {
|
|
$user = $user->ID;
|
|
}
|
|
|
|
if ( ! is_null( $this->user_can_use_media_actions ) && isset( $this->user_can_use_media_actions[ $user ] ) ) {
|
|
return $this->user_can_use_media_actions[ $user ];
|
|
}
|
|
|
|
$this->user_can_use_media_actions[ $user ] = false;
|
|
|
|
if ( user_can( $user, 'use_as3cf_media_actions' ) ) {
|
|
$this->user_can_use_media_actions[ $user ] = true;
|
|
} else {
|
|
/**
|
|
* The default capability for using on-demand S3 media actions.
|
|
*
|
|
* @param string $capability Registered capability identifier
|
|
*/
|
|
$capability = apply_filters( 'as3cfpro_media_actions_capability', 'manage_options' );
|
|
$this->user_can_use_media_actions[ $user ] = user_can( $user, $capability );
|
|
}
|
|
|
|
return $this->user_can_use_media_actions[ $user ];
|
|
}
|
|
|
|
/**
|
|
* Generate the URL for performing S3 media actions
|
|
*
|
|
* @param string $action
|
|
* @param int $post_id
|
|
* @param null|string $sendback_path
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_media_action_url( $action, $post_id, $sendback_path = null ) {
|
|
$args = array(
|
|
'action' => $action,
|
|
'ids' => $post_id,
|
|
);
|
|
|
|
if ( ! is_null( $sendback_path ) ) {
|
|
$args['sendback'] = urlencode( admin_url( $sendback_path ) );
|
|
}
|
|
|
|
$url = add_query_arg( $args, admin_url( 'upload.php' ) );
|
|
$url = wp_nonce_url( $url, 'as3cfpro-' . $action );
|
|
|
|
return esc_url( $url );
|
|
}
|
|
|
|
/**
|
|
* Handle S3 actions applied to attachments via the Backbone JS
|
|
* in the media grid and edit attachment modal
|
|
*/
|
|
public function ajax_process_media_action() {
|
|
if ( ! isset( $_POST['s3_action'] ) && ! isset( $_POST['ids'] ) ) {
|
|
return;
|
|
}
|
|
|
|
$scope = filter_input( INPUT_POST, 'scope' );
|
|
$action = filter_input( INPUT_POST, 's3_action' );
|
|
|
|
check_ajax_referer( "{$scope}-{$action}" );
|
|
|
|
$ids = array_map( 'intval', $_POST['ids'] ); // input var okay
|
|
|
|
// process the S3 action for the attachments
|
|
$return = $this->maybe_do_provider_action( $action, $ids, true );
|
|
|
|
$message_html = '';
|
|
|
|
if ( $return ) {
|
|
$message_html = $this->get_media_action_result_message( $action, $return['count'], $return['errors'] );
|
|
}
|
|
|
|
wp_send_json_success( $message_html );
|
|
}
|
|
|
|
/**
|
|
* Wrapper for media actions
|
|
*
|
|
* @param string $action type of media action, copy, remove, download, remove_local
|
|
* @param array $ids attachment IDs
|
|
* @param bool $doing_bulk_action flag for multiple attachments, if true then we need to
|
|
* perform a check for each attachment
|
|
*
|
|
* @return bool|array on success array with success count and error count
|
|
* @throws Exception
|
|
*/
|
|
public function maybe_do_provider_action( $action, $ids, $doing_bulk_action ) {
|
|
switch ( $action ) {
|
|
case 'copy':
|
|
$result = $this->maybe_upload_attachments( $ids, $doing_bulk_action );
|
|
break;
|
|
case 'remove':
|
|
$result = $this->maybe_delete_attachments_from_provider( $ids, $doing_bulk_action );
|
|
break;
|
|
case 'download':
|
|
$result = $this->maybe_download_attachments_from_provider( $ids, $doing_bulk_action );
|
|
break;
|
|
case 'private_acl':
|
|
$result = $this->maybe_update_acls_to_private( $ids, $doing_bulk_action );
|
|
break;
|
|
case 'public_acl':
|
|
$result = $this->maybe_update_acls_to_public( $ids, $doing_bulk_action );
|
|
break;
|
|
case 'remove_local':
|
|
$result = $this->maybe_remove_local_files_for_attachments( $ids, $doing_bulk_action );
|
|
break;
|
|
default:
|
|
// not one of our actions, remove
|
|
$result = false;
|
|
break;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Get the result message after an S3 action has been performed
|
|
*
|
|
* @param string $action type of S3 action
|
|
* @param int $count count of successful processes
|
|
* @param int $error_count count of errors
|
|
*
|
|
* @return bool|string
|
|
*/
|
|
public function get_media_action_result_message( $action, $count = 0, $error_count = 0 ) {
|
|
$class = 'updated';
|
|
$type = 'success';
|
|
|
|
if ( 0 === $count && 0 === $error_count ) {
|
|
// don't show any message if no attachments processed
|
|
// i.e. they haven't met the checks for bulk actions
|
|
return false;
|
|
}
|
|
|
|
if ( $error_count > 0 ) {
|
|
$type = 'error';
|
|
$class = $type;
|
|
|
|
// We have processed some successfully.
|
|
if ( $count > 0 ) {
|
|
$type = 'partial';
|
|
}
|
|
}
|
|
|
|
$message = $this->get_message( $action, $type );
|
|
|
|
// can't find a relevant message, abort
|
|
if ( ! $message ) {
|
|
return false;
|
|
}
|
|
|
|
$id = $this->as3cf->filter_input( 'as3cf_id', INPUT_GET, FILTER_VALIDATE_INT );
|
|
|
|
// If we're uploading a single item, add an edit link.
|
|
if ( 1 === ( $count + $error_count ) && ! empty( $id ) ) {
|
|
$url = esc_url( get_edit_post_link( $id ) );
|
|
|
|
// Only add the link if we have a URL.
|
|
if ( ! empty( $url ) ) {
|
|
$text = esc_html__( 'Edit attachment', 'amazon-s3-and-cloudfront' );
|
|
$message .= sprintf( ' <a href="%1$s">%2$s</a>', $url, $text );
|
|
}
|
|
}
|
|
|
|
$message = sprintf( '<div class="notice as3cf-notice %s is-dismissible"><p>%s</p></div>', $class, $message );
|
|
|
|
return $message;
|
|
}
|
|
|
|
/**
|
|
* Retrieve all the media action related notice messages
|
|
*
|
|
* @return array
|
|
*/
|
|
public function get_messages() {
|
|
if ( is_null( $this->messages ) ) {
|
|
$this->messages = array(
|
|
'copy' => array(
|
|
'success' => __( 'Media successfully copied to bucket.', 'amazon-s3-and-cloudfront' ),
|
|
'partial' => __( 'Media copied to bucket with some errors.', 'amazon-s3-and-cloudfront' ),
|
|
'error' => __( 'There were errors when copying the media to bucket.', 'amazon-s3-and-cloudfront' ),
|
|
),
|
|
'remove' => array(
|
|
'success' => __( 'Media successfully removed from bucket.', 'amazon-s3-and-cloudfront' ),
|
|
'partial' => __( 'Media removed from bucket, with some errors.', 'amazon-s3-and-cloudfront' ),
|
|
'error' => __( 'There were errors when removing the media from bucket.', 'amazon-s3-and-cloudfront' ),
|
|
),
|
|
'download' => array(
|
|
'success' => __( 'Media successfully downloaded from bucket.', 'amazon-s3-and-cloudfront' ),
|
|
'partial' => __( 'Media downloaded from bucket, with some errors.', 'amazon-s3-and-cloudfront' ),
|
|
'error' => __( 'There were errors when downloading the media from bucket.', 'amazon-s3-and-cloudfront' ),
|
|
),
|
|
'private_acl' => array(
|
|
'success' => __( 'Media successfully set as private in bucket.', 'amazon-s3-and-cloudfront' ),
|
|
'partial' => __( 'Media set as private in bucket, with some errors.', 'amazon-s3-and-cloudfront' ),
|
|
'error' => __( 'There were errors when setting the media as private in bucket.', 'amazon-s3-and-cloudfront' ),
|
|
),
|
|
'public_acl' => array(
|
|
'success' => __( 'Media successfully set as public in bucket.', 'amazon-s3-and-cloudfront' ),
|
|
'partial' => __( 'Media set as public in bucket, with some errors.', 'amazon-s3-and-cloudfront' ),
|
|
'error' => __( 'There were errors when setting the media as public in bucket.', 'amazon-s3-and-cloudfront' ),
|
|
),
|
|
'remove_local' => array(
|
|
'success' => __( 'Media successfully removed from server.', 'amazon-s3-and-cloudfront' ),
|
|
'partial' => __( 'Media removed from server, with some errors.', 'amazon-s3-and-cloudfront' ),
|
|
'error' => __( 'There were errors when removing the media from server.', 'amazon-s3-and-cloudfront' ),
|
|
),
|
|
);
|
|
}
|
|
|
|
return $this->messages;
|
|
}
|
|
|
|
/**
|
|
* Get a specific media action notice message
|
|
*
|
|
* @param string $action type of action, e.g. copy, remove, download
|
|
* @param string $type if the action has resulted in success, error, partial (errors)
|
|
*
|
|
* @return string|bool
|
|
*/
|
|
public function get_message( $action = 'copy', $type = 'success' ) {
|
|
$messages = $this->get_messages();
|
|
if ( isset( $messages[ $action ][ $type ] ) ) {
|
|
return $messages[ $action ][ $type ];
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Wrapper for uploading multiple attachments to S3
|
|
*
|
|
* @param array $post_ids attachment IDs
|
|
* @param bool $doing_bulk_action flag for multiple attachments, if true then we need to
|
|
* perform a check for each attachment to make sure the
|
|
* file exists locally before uploading to S3
|
|
*
|
|
* @return array|WP_Error
|
|
* @throws Exception
|
|
*/
|
|
public function maybe_upload_attachments( $post_ids, $doing_bulk_action = false ) {
|
|
$result = array(
|
|
'errors' => 0,
|
|
'count' => 0,
|
|
);
|
|
$upload_handler = $this->as3cf->get_item_handler( Upload_Handler::get_item_handler_key_name() );
|
|
|
|
foreach ( $post_ids as $post_id ) {
|
|
if ( $doing_bulk_action ) {
|
|
// If bulk action check the file exists.
|
|
$file = get_attached_file( $post_id, true );
|
|
// If the file doesn't exist locally we can't copy.
|
|
if ( ! file_exists( $file ) ) {
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$offloaded_files = array();
|
|
$as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
|
|
|
|
if ( empty( $as3cf_item ) ) {
|
|
$as3cf_item = Media_Library_Item::create_from_source_id( $post_id );
|
|
} else {
|
|
// Refresh the list of expected objects from attachment's expected thumbnail sizes.
|
|
$offloaded_files = $as3cf_item->offloaded_files();
|
|
$metadata = wp_get_attachment_metadata( $post_id, true );
|
|
|
|
if ( empty( $metadata ) || is_wp_error( $metadata ) ) {
|
|
$result['errors']++;
|
|
continue;
|
|
}
|
|
|
|
$this->update_item_from_new_metadata( $as3cf_item, $metadata );
|
|
}
|
|
|
|
if ( ! empty( $as3cf_item ) && ! is_wp_error( $as3cf_item ) ) {
|
|
$upload_result = $upload_handler->handle( $as3cf_item, $offloaded_files );
|
|
|
|
// Even if an upload was cancelled via a filter,
|
|
// if there was no error, then it was successfully processed.
|
|
if ( is_wp_error( $upload_result ) ) {
|
|
AS3CF_Error::log( $upload_result->get_error_messages() );
|
|
} else {
|
|
$result['count']++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$result['errors']++;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Wrapper for removing multiple attachments from provider.
|
|
*
|
|
* @param array $post_ids Attachment IDs
|
|
* @param bool $doing_bulk_action Flag for multiple attachments. If true then we need to
|
|
* perform a check for each attachment to make sure it has
|
|
* been uploaded to provider before trying to delete it.
|
|
* This flag currently has no effect, but is retained for
|
|
* consistency with similar functions in this class.
|
|
*
|
|
* @return array
|
|
* @throws Exception
|
|
*/
|
|
public function maybe_delete_attachments_from_provider( $post_ids, $doing_bulk_action = false ) {
|
|
$result = array(
|
|
'errors' => 0,
|
|
'count' => 0,
|
|
);
|
|
|
|
/** @var Remove_Provider_Handler $remove_handler */
|
|
$remove_handler = $this->as3cf->get_item_handler( Remove_Provider_Handler::get_item_handler_key_name() );
|
|
|
|
foreach ( $post_ids as $post_id ) {
|
|
$as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
|
|
if ( empty( $as3cf_item ) ) {
|
|
$result['count']++;
|
|
continue;
|
|
}
|
|
|
|
// Delete attachment from provider.
|
|
$remove_result = $remove_handler->handle( $as3cf_item );
|
|
if ( is_wp_error( $remove_result ) ) {
|
|
$result['errors']++;
|
|
AS3CF_Error::log( $remove_result->get_error_messages() );
|
|
continue;
|
|
}
|
|
|
|
// There was no error, so either objects deleted or skipped due to duplicates.
|
|
$as3cf_item->delete();
|
|
|
|
if ( Media_Library_Item::get_by_source_id( $post_id ) ) {
|
|
$result['errors']++;
|
|
continue;
|
|
}
|
|
|
|
$result['count']++;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Wrapper for downloading multiple attachments from provider.
|
|
*
|
|
* @param array $post_ids Attachment IDs
|
|
* @param bool $doing_bulk_action Flag for multiple attachments. If true then we need to
|
|
* perform a check for each attachment to make sure it has
|
|
* been uploaded to provider and does not exist locally before
|
|
* trying to download it.
|
|
*
|
|
* @return array
|
|
* @throws Exception
|
|
*/
|
|
public function maybe_download_attachments_from_provider( $post_ids, $doing_bulk_action = false ) {
|
|
$result = array(
|
|
'errors' => 0,
|
|
'count' => 0,
|
|
);
|
|
|
|
$download_handler = $this->as3cf->get_item_handler( Download_Handler::get_item_handler_key_name() );
|
|
|
|
foreach ( $post_ids as $post_id ) {
|
|
$file = get_attached_file( $post_id, true );
|
|
$as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
|
|
|
|
if ( empty( $as3cf_item ) || is_wp_error( $as3cf_item ) ) {
|
|
continue;
|
|
}
|
|
|
|
// If bulk action, check whether original file exists locally before trying to download.
|
|
$file_exists_locally = $doing_bulk_action && file_exists( $file );
|
|
|
|
if ( ! $file_exists_locally ) {
|
|
// Download the attachment from provider.
|
|
$download_handler->handle( $as3cf_item );
|
|
|
|
if ( ! file_exists( $file ) ) {
|
|
$result['errors']++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$result['count']++;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Wrapper for removing multiple attachments from server if offloaded
|
|
*
|
|
* @param array $post_ids attachment IDs
|
|
* @param bool $doing_bulk_action flag for multiple attachments, if true then we need to
|
|
* perform a check for each attachment to make sure it has
|
|
* been uploaded to bucket before trying to delete it
|
|
*
|
|
* @return array
|
|
* @throws Exception
|
|
*/
|
|
public function maybe_remove_local_files_for_attachments( $post_ids, $doing_bulk_action = false ) {
|
|
$result = array(
|
|
'errors' => 0,
|
|
'count' => 0,
|
|
);
|
|
$as3cf_items = array();
|
|
|
|
foreach ( $post_ids as $key => $post_id ) {
|
|
$as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
|
|
if ( empty( $as3cf_item ) ) {
|
|
$result['errors']++;
|
|
unset( $post_ids[ $key ] );
|
|
continue;
|
|
}
|
|
|
|
$as3cf_items[ $post_id ] = $as3cf_item;
|
|
}
|
|
|
|
if ( ! empty( $post_ids ) ) {
|
|
/** @var Remove_Local_Handler $remove_local_handler */
|
|
$remove_local_handler = $this->as3cf->get_item_handler( Remove_Local_Handler::get_item_handler_key_name() );
|
|
|
|
// Set up to check provider keys before removing.
|
|
$options = array(
|
|
'verify_exists_on_provider' => true,
|
|
'provider_keys' => $this->as3cf->get_provider_keys( $post_ids ),
|
|
);
|
|
|
|
foreach ( $post_ids as $post_id ) {
|
|
$as3cf_item = $as3cf_items[ $post_id ];
|
|
$remove_local_handler->handle( $as3cf_item, $options );
|
|
|
|
if ( ! $this->attachment_exists_locally( $post_id ) ) {
|
|
$result['count']++;
|
|
} else {
|
|
$result['errors']++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Wrapper for updating the ACLs for multiple attachments on provider
|
|
*
|
|
* @param array $post_ids Attachment IDs
|
|
* @param bool $doing_bulk_action Flag for multiple attachments, if true then we need to
|
|
* perform a check for each attachment to make sure it has
|
|
* been uploaded to the current provider
|
|
* @param bool $private Setting to private ACL? Default is public.
|
|
*
|
|
* @return array|WP_Error
|
|
* @throws Exception
|
|
*/
|
|
private function maybe_update_acls( $post_ids, $doing_bulk_action = false, $private = false ) {
|
|
$result = array(
|
|
'errors' => 0,
|
|
'count' => 0,
|
|
);
|
|
|
|
$provider_key = $this->as3cf->get_storage_provider()->get_provider_key_name();
|
|
/** @var Update_Acl_Handler $update_acl_handler */
|
|
$update_acl_handler = $this->as3cf->get_item_handler( Update_Acl_Handler::get_item_handler_key_name() );
|
|
$options = array( 'set_private' => $private );
|
|
|
|
foreach ( $post_ids as $post_id ) {
|
|
$as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
|
|
|
|
if ( ! $as3cf_item ) {
|
|
$result['errors']++;
|
|
continue;
|
|
}
|
|
|
|
if ( $doing_bulk_action ) {
|
|
if ( $provider_key !== $as3cf_item->provider() ) {
|
|
$result['errors']++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
$update_result = $update_acl_handler->handle( $as3cf_item, $options );
|
|
|
|
if ( is_wp_error( $update_result ) ) {
|
|
$result['errors']++;
|
|
AS3CF_Error::log( $update_result->get_error_messages() );
|
|
continue;
|
|
}
|
|
|
|
$result['count']++;
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
/**
|
|
* Wrapper for updating the ACLs to private for multiple attachments on provider
|
|
*
|
|
* @param array $post_ids Attachment IDs
|
|
* @param bool $doing_bulk_action Flag for multiple attachments, if true then we need to
|
|
* perform a check for each attachment to make sure it has
|
|
* been uploaded to the current provider
|
|
*
|
|
* @return array|WP_Error
|
|
* @throws Exception
|
|
*/
|
|
private function maybe_update_acls_to_private( $post_ids, $doing_bulk_action = false ) {
|
|
return $this->maybe_update_acls( $post_ids, $doing_bulk_action, true );
|
|
}
|
|
|
|
/**
|
|
* Wrapper for updating the ACLs to public for multiple attachments on provider
|
|
*
|
|
* @param array $post_ids Attachment IDs
|
|
* @param bool $doing_bulk_action Flag for multiple attachments, if true then we need to
|
|
* perform a check for each attachment to make sure it has
|
|
* been uploaded to the current provider
|
|
*
|
|
* @return array|WP_Error
|
|
* @throws Exception
|
|
*/
|
|
private function maybe_update_acls_to_public( $post_ids, $doing_bulk_action = false ) {
|
|
return $this->maybe_update_acls( $post_ids, $doing_bulk_action, false );
|
|
}
|
|
|
|
/**
|
|
* Get attachment local paths.
|
|
*
|
|
* @param int $attachment_id
|
|
*
|
|
* @return array
|
|
*/
|
|
private function get_attachment_local_paths( $attachment_id ) {
|
|
static $local_paths = array();
|
|
|
|
$blog_id = get_current_blog_id();
|
|
|
|
if ( isset( $local_paths[ $blog_id ][ $attachment_id ] ) ) {
|
|
return $local_paths[ $blog_id ][ $attachment_id ];
|
|
}
|
|
|
|
$paths = AS3CF_Utils::get_attachment_file_paths( $attachment_id, false );
|
|
|
|
$local_paths[ $blog_id ][ $attachment_id ] = array_unique( $paths );
|
|
|
|
return $paths;
|
|
}
|
|
|
|
/**
|
|
* Does attachment exist locally?
|
|
*
|
|
* @param int $attachment_id
|
|
*
|
|
* @return bool
|
|
*/
|
|
private function attachment_exists_locally( $attachment_id ) {
|
|
$paths = $this->get_attachment_local_paths( $attachment_id );
|
|
|
|
foreach ( $paths as $path ) {
|
|
if ( file_exists( $path ) ) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Get ACL value string.
|
|
*
|
|
* @param array $acl
|
|
* @param int $post_id
|
|
*
|
|
* @return string
|
|
*/
|
|
public function get_acl_value_string( $acl, $post_id ) {
|
|
$as3cf_item = Media_Library_Item::get_by_source_id( $post_id );
|
|
if ( ! in_array( 'update_acl', $this->get_available_media_actions( 'singular' ) ) || ! $this->as3cf || ! $as3cf_item->served_by_provider( true ) ) {
|
|
return parent::get_acl_value_string( $acl, $post_id );
|
|
}
|
|
|
|
return sprintf( '<a id="as3cfpro-toggle-acl" title="%s" data-currentACL="%s" href="#">%s</a>', $acl['title'], $acl['acl'], $acl['name'] );
|
|
}
|
|
|
|
/**
|
|
* Helper function for terminating script execution. Easily testable.
|
|
*
|
|
* @param int|string $exit_code
|
|
*
|
|
* @return void
|
|
*
|
|
* phpcs:disable PSR2.Methods.MethodDeclaration.Underscore
|
|
*/
|
|
public function _exit( $exit_code = 0 ) {
|
|
exit( $exit_code );
|
|
}
|
|
|
|
/**
|
|
* Add column with as3cf item details to the media library list view.
|
|
*
|
|
* @handles manage_media_columns
|
|
*
|
|
* @param array $columns
|
|
*
|
|
* @return array
|
|
*/
|
|
public function add_media_library_columns( array $columns ): array {
|
|
return array_merge( $columns, $this->managed_columns );
|
|
}
|
|
|
|
/**
|
|
* Add our columns as sortable.
|
|
*
|
|
* @handles manage_upload_sortable_columns
|
|
*
|
|
* @param array $columns
|
|
*
|
|
* @return array
|
|
*/
|
|
public function add_media_library_sortable_columns( array $columns ): array {
|
|
foreach ( $this->managed_columns as $key => $caption ) {
|
|
$columns[ $key ] = $key;
|
|
}
|
|
|
|
return $columns;
|
|
}
|
|
|
|
/**
|
|
* Render column content for individual media library item.
|
|
*
|
|
* @handles manage_media_custom_column
|
|
*
|
|
* @param string $column_name
|
|
* @param string $post_id
|
|
*/
|
|
public function get_media_library_column_content( string $column_name, string $post_id ) {
|
|
if ( ! in_array( $column_name, array_keys( $this->managed_columns ) ) ) {
|
|
return;
|
|
}
|
|
|
|
$provider_object = $this->get_formatted_provider_info( $post_id );
|
|
if ( empty( $provider_object ) ) {
|
|
echo '—';
|
|
|
|
return;
|
|
}
|
|
|
|
// Bucket column.
|
|
if ( $column_name === 'as3cf_bucket' ) {
|
|
echo sprintf(
|
|
'%s<br>%s<br>%s',
|
|
$provider_object['provider_name'],
|
|
$provider_object['region'],
|
|
$provider_object['bucket']
|
|
);
|
|
|
|
if ( ! empty( $this->bucket_col_actions ) ) {
|
|
echo '<div class="row-actions">' . join( ' | ', $this->bucket_col_actions ) . '</div>';
|
|
}
|
|
}
|
|
|
|
// Access column.
|
|
if ( $column_name === 'as3cf_access' ) {
|
|
echo sprintf(
|
|
'%s',
|
|
$provider_object['acl']['name']
|
|
);
|
|
|
|
if ( ! empty( $this->access_col_actions ) ) {
|
|
echo '<div class="row-actions">' . join( ' | ', $this->access_col_actions ) . '</div>';
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Renders the filter dropdowns in the media library list view.
|
|
*
|
|
* @handles restrict_manage_posts
|
|
*/
|
|
public function add_media_library_filters() {
|
|
$screen = get_current_screen();
|
|
if ( 'upload' !== $screen->base ) {
|
|
return;
|
|
}
|
|
|
|
$filters = $this->get_media_library_filters_config();
|
|
|
|
echo AS3CF_Utils::make_dropdown( $filters['as3cf_location'] );
|
|
echo AS3CF_Utils::make_dropdown( $filters['as3cf_access'] );
|
|
}
|
|
|
|
/**
|
|
* Filter the posts query when one of our filters are in use on the
|
|
* media library screen.
|
|
*
|
|
* @handles pre_get_posts
|
|
*
|
|
* @param WP_Query $query
|
|
*
|
|
* @return WP_Query
|
|
*/
|
|
public function media_library_filter_query( WP_Query $query ): WP_Query {
|
|
global $pagenow;
|
|
|
|
if ( empty( $query->query_vars["post_type"] ) || 'attachment' !== $query->query_vars["post_type"] ) {
|
|
return $query;
|
|
}
|
|
|
|
if ( ! in_array( $pagenow, array( 'upload.php', 'admin-ajax.php' ) ) ) {
|
|
return $query;
|
|
}
|
|
|
|
$location = $this->selected_filter_value( 'as3cf_location', 'all' );
|
|
$access = $this->selected_filter_value( 'as3cf_access', 'all' );
|
|
$orderby = $query->get( 'orderby' );
|
|
|
|
// If none of our filters are in play and sorting isn't by one of our columns, exit out.
|
|
if ( 'all' === $location && 'all' === $access && ! in_array( $orderby, array_keys( $this->managed_columns ) ) ) {
|
|
return $query;
|
|
}
|
|
|
|
add_filter( 'posts_join', array( $this, 'query_join_items_table' ), 10, 2 );
|
|
add_filter( 'posts_where', array( $this, 'query_add_where' ), 10, 2 );
|
|
add_filter( 'posts_orderby', array( $this, 'query_add_orderby' ), 10, 2 );
|
|
|
|
return $query;
|
|
}
|
|
|
|
/**
|
|
* Join in the items table in WP_Query.
|
|
*
|
|
* @handles posts_join
|
|
*
|
|
* @param string $join
|
|
* @param WP_Query $query
|
|
*
|
|
* @return string
|
|
*/
|
|
public function query_join_items_table( string $join, WP_Query $query ): string {
|
|
global $wpdb;
|
|
|
|
$table = $wpdb->get_blog_prefix() . Item::ITEMS_TABLE;
|
|
$join .= " LEFT JOIN {$table} i ON (i.source_type = 'media-library' AND i.source_id = {$wpdb->posts}.ID) ";
|
|
|
|
return $join;
|
|
}
|
|
|
|
/**
|
|
* Add where clauses to posts query.
|
|
*
|
|
* @handles posts_where
|
|
*
|
|
* @param string $where
|
|
* @param WP_Query $query
|
|
*
|
|
* @return string
|
|
*/
|
|
public function query_add_where( string $where, WP_Query $query ): string {
|
|
global $wpdb;
|
|
|
|
$location = $this->selected_filter_value( 'as3cf_location', 'all' );
|
|
switch ( $location ) {
|
|
case 'all':
|
|
// intentionally empty
|
|
break;
|
|
case 'offloaded':
|
|
$where .= ' AND i.id IS NOT NULL ';
|
|
break;
|
|
case 'not_offloaded':
|
|
$where .= ' AND i.id IS NULL ';
|
|
break;
|
|
default:
|
|
$parts = explode( '|', $location );
|
|
if ( count( $parts ) === 2 ) {
|
|
array_walk( $parts, 'sanitize_key' );
|
|
$where .= $wpdb->prepare( ' AND (i.provider = %s AND i.bucket = %s) ', $parts[0], $parts[1] );
|
|
}
|
|
}
|
|
|
|
$access = $this->selected_filter_value( 'as3cf_access', 'all' );
|
|
switch ( $access ) {
|
|
case 'all':
|
|
// intentionally empty
|
|
break;
|
|
case 'private':
|
|
$where .= ' AND i.is_private = 1 ';
|
|
break;
|
|
case 'public':
|
|
$where .= ' AND i.is_private = 0 ';
|
|
break;
|
|
}
|
|
|
|
return $where;
|
|
}
|
|
|
|
/**
|
|
* Add ORDER BY clause to posts query.
|
|
*
|
|
* @handles posts_orderby
|
|
*
|
|
* @param string $orderby
|
|
* @param WP_Query $query
|
|
*
|
|
* @return string
|
|
*/
|
|
public function query_add_orderby( string $orderby, WP_Query $query ): string {
|
|
$direction = $query->get( 'order', 'ASC' );
|
|
|
|
switch ( $query->get( 'orderby' ) ) {
|
|
case 'as3cf_access':
|
|
$orderby = sprintf( 'i.is_private %s', $direction );
|
|
break;
|
|
case 'as3cf_bucket':
|
|
$orderby = sprintf( 'i.bucket %1$s, i.provider %1$s', $direction );
|
|
break;
|
|
}
|
|
|
|
return $orderby;
|
|
}
|
|
|
|
/**
|
|
* Get all providers and buckets currently in use.
|
|
*
|
|
* @return array
|
|
*/
|
|
private function get_providers_and_buckets(): array {
|
|
$blog_id = get_current_blog_id();
|
|
$counts = $this->as3cf->media_counts();
|
|
|
|
if ( ! empty( $counts['providers'][ $blog_id ] ) ) {
|
|
return $counts['providers'][ $blog_id ];
|
|
}
|
|
|
|
return array();
|
|
}
|
|
|
|
/**
|
|
* Update media counts cache with current provider and bucket usage.
|
|
*
|
|
* @param array $media_counts
|
|
* @param int $blog_id
|
|
* @param string $table_prefix
|
|
*
|
|
* @return array
|
|
*/
|
|
public function add_providers_and_buckets_to_media_counts( $media_counts, $blog_id, $table_prefix ): array {
|
|
global $wpdb;
|
|
|
|
if ( ! is_array( $media_counts ) || ! is_int( $blog_id ) || empty( $table_prefix ) || ! is_string( $table_prefix ) ) {
|
|
return $media_counts;
|
|
}
|
|
|
|
$table = $table_prefix . Item::ITEMS_TABLE;
|
|
$rows = $wpdb->get_results( "SELECT DISTINCT provider, bucket FROM $table ORDER BY provider, bucket" );
|
|
|
|
$providers = array_unique( array_map( function ( $row ) {
|
|
return $row->provider;
|
|
}, $rows ) );
|
|
|
|
$all_buckets = array();
|
|
foreach ( $providers as $provider_key ) {
|
|
/** @var Provider|null $provider_class */
|
|
$provider_class = $this->as3cf->get_provider_class( $provider_key );
|
|
$provider_name = is_null( $provider_class ) ? $provider_key : $provider_class::get_provider_service_name();
|
|
|
|
foreach ( $rows as $row ) {
|
|
if ( $row->provider !== $provider_key ) {
|
|
continue;
|
|
}
|
|
|
|
$bucket_key = $row->provider . '|' . $row->bucket;
|
|
|
|
$all_buckets[ $provider_name ][ $bucket_key ] = $row->bucket;
|
|
}
|
|
}
|
|
|
|
$media_counts['providers'][ $blog_id ] = $all_buckets;
|
|
|
|
return $media_counts;
|
|
}
|
|
|
|
/**
|
|
* Safely retrieve the selected filter value from a dropdown.
|
|
*
|
|
* @param string $key
|
|
* @param string $default
|
|
*
|
|
* @return string
|
|
*/
|
|
private function selected_filter_value( string $key, string $default ): string {
|
|
if ( wp_doing_ajax() ) {
|
|
if ( isset( $_REQUEST['query'][ $key ] ) ) {
|
|
$value = sanitize_text_field( $_REQUEST['query'][ $key ] );
|
|
}
|
|
} else {
|
|
if ( ! isset( $_REQUEST['filter_action'] ) || $_REQUEST['filter_action'] !== 'Filter' ) {
|
|
return $default;
|
|
}
|
|
|
|
if ( ! isset( $_REQUEST[ $key ] ) ) {
|
|
return $default;
|
|
}
|
|
|
|
$value = sanitize_text_field( $_REQUEST[ $key ] );
|
|
}
|
|
|
|
return ! empty( $value ) ? $value : $default;
|
|
}
|
|
|
|
/**
|
|
* Return values for the media filters.
|
|
*
|
|
* @return array
|
|
*/
|
|
private function get_media_library_filters_config(): array {
|
|
if ( ! empty( $this->media_library_filters ) ) {
|
|
return $this->media_library_filters;
|
|
}
|
|
|
|
// Locations dropdown.
|
|
$options = array(
|
|
'all' => _x( 'All locations', 'Media Library Filter option', 'amazon-s3-and-cloudfront' ),
|
|
'offloaded' => _x( 'Offloaded', 'Media Library Filter option', 'amazon-s3-and-cloudfront' ),
|
|
'not_offloaded' => _x( 'Not offloaded', 'Media Library Filter option', 'amazon-s3-and-cloudfront' ),
|
|
);
|
|
|
|
$options = array_merge( $options, $this->get_providers_and_buckets() );
|
|
|
|
$this->media_library_filters['as3cf_location'] = array(
|
|
'id' => 'as3cf_location',
|
|
'name' => 'as3cf_location',
|
|
'label' => __( 'Filter by Offload Media location', 'amazon-s3-and-cloudfront' ),
|
|
'selected' => $this->selected_filter_value( 'as3cf_location', 'all' ),
|
|
'options' => $options,
|
|
);
|
|
|
|
// Access dropdown.
|
|
$this->media_library_filters['as3cf_access'] = array(
|
|
'id' => 'as3cf_access',
|
|
'name' => 'as3cf_access',
|
|
'label' => __( 'Filter by Offload Media access level', 'amazon-s3-and-cloudfront' ),
|
|
'selected' => $this->selected_filter_value( 'as3cf_access', 'all' ),
|
|
'options' => array(
|
|
'all' => _x( 'All access', 'Media Library Filter option', 'amazon-s3-and-cloudfront' ),
|
|
'public' => _x( 'Public', 'Media Library Filter option', 'amazon-s3-and-cloudfront' ),
|
|
'private' => _x( 'Private', 'Media Library Filter option', 'amazon-s3-and-cloudfront' ),
|
|
),
|
|
);
|
|
|
|
return $this->media_library_filters;
|
|
}
|
|
|
|
/**
|
|
* Add URL entries for our summary counts.
|
|
*
|
|
* @handles as3cf_get_summary_counts
|
|
*
|
|
* @param array $summaries
|
|
*
|
|
* @return array
|
|
*/
|
|
public function add_urls_to_summary_counts( array $summaries ): array {
|
|
$attachments_url = add_query_arg(
|
|
array(
|
|
'mode' => 'list',
|
|
'filter_action' => 'Filter',
|
|
),
|
|
admin_url( 'upload.php' )
|
|
);
|
|
|
|
foreach ( $summaries as $idx => $summary ) {
|
|
if ( ! empty( $summary['type'] ) && Media_Library_Item::summary_type() === $summary['type'] ) {
|
|
$summaries[ $idx ]['offloaded_url'] = add_query_arg( 'as3cf_location', 'offloaded', $attachments_url );
|
|
$summaries[ $idx ]['not_offloaded_url'] = add_query_arg( 'as3cf_location', 'not_offloaded', $attachments_url );
|
|
break;
|
|
}
|
|
}
|
|
|
|
return $summaries;
|
|
}
|
|
}
|