Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

External Media: Updates for 8.7 #16069

Merged
merged 11 commits into from
Jun 25, 2020
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,32 @@ class WPCOM_REST_API_V2_Endpoint_External_Media extends WP_REST_Controller {
* @var array
*/
public $media_schema = array(
'items' => array(
'type' => 'object',
'required' => true,
'properties' => array(
'caption' => array(
'type' => 'string',
),
'guid' => array(
'items' => array(
'caption' => array(
'type' => 'string',
),
'name' => array(
'type' => 'string',
),
'title' => array(
'type' => 'string',
),
'url' => array(
'format' => 'uri',
'type' => 'string',
),
'type' => 'object',
'required' => true,
'properties' => array(
'caption' => array(
'type' => 'string',
),
'guid' => array(
'items' => array(
'caption' => array(
'type' => 'string',
),
'name' => array(
'type' => 'string',
),
'title' => array(
'type' => 'string',
),
'url' => array(
'format' => 'uri',
'type' => 'string',
),
'type' => 'array',
),
'title' => array(
'type' => 'string',
),
'type' => 'array',
),
'title' => array(
'type' => 'string',
),
),
);
Expand Down Expand Up @@ -116,11 +114,11 @@ public function register_routes() {
array(
'methods' => \WP_REST_Server::CREATABLE,
'callback' => array( $this, 'copy_external_media' ),
'permission_callback' => array( $this, 'permission_callback' ),
'permission_callback' => array( $this, 'create_item_permissions_check' ),
'args' => array(
'media' => array(
'description' => __( 'Media data to copy.', 'jetpack' ),
'items' => array_values( $this->media_schema ),
'items' => $this->media_schema,
'required' => true,
'type' => 'array',
'sanitize_callback' => array( $this, 'sanitize_media' ),
Expand Down Expand Up @@ -148,6 +146,42 @@ public function permission_callback() {
return current_user_can( 'edit_posts' );
}

/**
* Checks if a given request has access to create an attachment.
*
* @param WP_REST_Request $request Full details about the request.
* @return true|WP_Error True if the request has access to create items, WP_Error object otherwise.
*/
public function create_item_permissions_check( $request ) {
if ( ! empty( $request['id'] ) ) {
return new WP_Error(
'rest_post_exists',
__( 'Cannot create existing post.', 'jetpack' ),
array( 'status' => 400 )
);
}

$post_type = get_post_type_object( 'attachment' );

if ( ! current_user_can( $post_type->cap->create_posts ) ) {
return new WP_Error(
'rest_cannot_create',
__( 'Sorry, you are not allowed to create posts as this user.', 'jetpack' ),
array( 'status' => rest_authorization_required_code() )
);
}

if ( ! current_user_can( 'upload_files' ) ) {
return new WP_Error(
'rest_cannot_create',
__( 'Sorry, you are not allowed to upload media on this site.', 'jetpack' ),
array( 'status' => 400 )
);
}

return true;
}

/**
* Sanitization callback for media parameter.
*
Expand Down Expand Up @@ -231,6 +265,14 @@ function( $key ) {
$response = json_decode( wp_remote_retrieve_body( $response ) );
break;

case 401:
$response = new WP_Error(
'authorization_required',
__( 'You are not connected to that service.', 'jetpack' ),
array( 'status' => 403 )
);
break;

case 403:
$error = json_decode( wp_remote_retrieve_body( $response ) );
$response = new WP_Error( $error->code, $error->message, $error->data );
Expand Down
105 changes: 47 additions & 58 deletions extensions/shared/external-media/editor.scss
Original file line number Diff line number Diff line change
Expand Up @@ -17,44 +17,33 @@ $grid-size: 8px;
}
}

.jetpack-external-media-browser--visually-hidden {
position: absolute !important;
height: 1px;
width: 1px;
overflow: hidden;
clip: rect( 1px, 1px, 1px, 1px );
white-space: nowrap; /* added line */
}

/**
* Media item container
* Media button menu
*/

.jetpack-external-media-browser__is-copying {
max-height: 20%;
max-width: 20%;
min-width: 480px;
min-height: 360px + 56px;

.jetpack-external-media-browser__single {
height: 300px;
}

.jetpack-external-media-browser__media {
height: 100%;
}

.is-transient {
border: 0;
background: 0;
padding: 0;
height: 100%;

&.jetpack-external-media-browser__media__item__selected {
box-shadow: none;
border-radius: 0;
}

img {
width: 100%;
height: 100%;
object-fit: contain;
}
}
/**
* Hide the menu when the modal is open. Hacky, but necessary to allow the focus
* to return to selected option when modal is closed. This is how it's currenly
* also implemented in Gutenberg's MediaReplaceFlow.
*/
.modal-open .jetpack-external-media-button-menu__options {
display: none;
}

.jetpack-external-media-browser:not( .jetpack-external-media-browser__is-copying ) {
/**
* Media item container
*/

.jetpack-external-media-browser {
.is-error {
margin-bottom: 1em;
margin-left: 0;
Expand All @@ -71,29 +60,16 @@ $grid-size: 8px;
}
}

@media ( min-width: 600px ) {
.jetpack-external-media-browser .components-modal__content {
width: 90vw;
height: 90vh;
}
.jetpack-external-media-browser--is-copying {
pointer-events: none;
}

.jetpack-external-media-browser__single {
position: relative;
display: flex;
justify-content: center;
align-items: center;

.is-transient img {
opacity: 0.3;
}
.components-modal__content {
width: 100%;

.components-spinner {
position: absolute;
top: 50%;
right: 50%;
margin-top: -9px;
margin-right: -9px;
@media ( min-width: 600px ) {
width: 90vw;
height: 90vh;
}
}

Expand Down Expand Up @@ -132,16 +108,29 @@ $grid-size: 8px;
&.is-transient img {
opacity: 0.3;
}
}

.jetpack-external-media-browser__media__copying_indicator {
display: flex;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
flex-direction: column;
justify-content: center;
align-items: center;
text-align: center;

.components-spinner {
position: absolute;
top: 50%;
right: 50%;
margin-top: -9px;
margin-right: -9px;
margin-bottom: $grid-size;
}
}

.jetpack-external-media-browser__media__copying_indicator__label {
font-size: 12px;
}

.jetpack-external-media-browser__media__folder {
float: left;
display: flex;
Expand Down
5 changes: 3 additions & 2 deletions extensions/shared/external-media/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,11 @@ if ( isCurrentUserConnected() ) {
'editor.MediaUpload',
'external-media/replace-media-upload',
OriginalComponent => props => {
const { allowedTypes, gallery = false, value = false } = props;
let { render } = props;

// Only replace button for components that expect images.
if ( props.allowedTypes.indexOf( 'image' ) > -1 ) {
// Only replace button for components that expect images, except existing galleries.
if ( allowedTypes.indexOf( 'image' ) > -1 && ! ( gallery && value ) ) {
render = button => <MediaButton { ...button } mediaProps={ props } />;
}

Expand Down
42 changes: 33 additions & 9 deletions extensions/shared/external-media/media-browser/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,17 @@ const EmptyResults = memo( () => (
) );

function MediaBrowser( props ) {
const { media, isLoading, pageHandle, className, multiple, setPath, nextPage, onCopy } = props;
const {
media,
isCopying,
isLoading,
pageHandle,
className,
multiple,
setPath,
nextPage,
onCopy,
} = props;
const [ selected, setSelected ] = useState( [] );

const onSelectImage = useCallback(
Expand Down Expand Up @@ -70,6 +80,25 @@ function MediaBrowser( props ) {
nextPage();
};

const SelectButton = () => {
const disabled = selected.length === 0 || isCopying;
const label = isCopying ? __( 'Inserting…', 'jetpack' ) : __( 'Select', 'jetpack' );

return (
<div className="jetpack-external-media-browser__media__toolbar">
<Button
isPrimary
isLarge
isBusy={ isCopying }
disabled={ disabled }
onClick={ onCopyAndInsert }
>
{ label }
</Button>
</div>
);
};

return (
<div className={ wrapper }>
<ul className={ classes }>
Expand All @@ -80,6 +109,7 @@ function MediaBrowser( props ) {
onClick={ onSelectImage }
focusOnMount={ !! prevMediaCount.current && index === prevMediaCount.current }
isSelected={ selected.find( toFind => toFind.ID === item.ID ) }
isCopying={ isCopying }
/>
) ) }

Expand All @@ -91,21 +121,15 @@ function MediaBrowser( props ) {
isLarge
isSecondary
className="jetpack-external-media-browser__loadmore"
disabled={ isLoading }
disabled={ isLoading || isCopying }
onClick={ onLoadMoreClick }
>
{ __( 'Load More', 'jetpack' ) }
</Button>
) }
</ul>

{ hasMediaItems && (
<div className="jetpack-external-media-browser__media__toolbar">
<Button isPrimary isLarge disabled={ selected.length === 0 } onClick={ onCopyAndInsert }>
{ __( 'Copy & Insert', 'jetpack' ) }
</Button>
</div>
) }
{ hasMediaItems && <SelectButton /> }
</div>
);
}
Expand Down
Loading