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

Inserter: Restrict unallowed block types more broadly #4097

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 17 additions & 6 deletions blocks/autocompleters/index.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
import { sortBy } from 'lodash';
import { includes, sortBy } from 'lodash';

/**
* Internal dependencies
Expand Down Expand Up @@ -58,17 +58,28 @@ import BlockIcon from '../block-icon';
*/

/**
* Returns an "completer" definition for selecting from available blocks to replace the current one.
* The definition can be understood by the Autocomplete component.
* Returns completer definition for selecting from available blocks to replace
* the current one, understood by the Autocomplete component.
*
* @param {Function} onReplace Callback to replace the current block.
* @param {Function} onReplace Callback to replace
* the current block
* @param {(Boolean|String[])} options.allowedBlockTypes Allowed block types
*
* @returns {Completer} Completer object used by the Autocomplete component.
*/
export function blockAutocompleter( { onReplace } ) {
export function blockAutocompleter( { onReplace, allowedBlockTypes = true } ) {
let blockTypes = getBlockTypes();

// Optionally filter allowed block types
if ( Array.isArray( allowedBlockTypes ) ) {
blockTypes = blockTypes.filter( ( blockType ) => (
includes( allowedBlockTypes, blockType.name )
) );
}

// Prioritize common category in block type options
const options = sortBy(
getBlockTypes(),
blockTypes,
( { category } ) => 'common' !== category
).map( ( blockType ) => {
const { name, title, icon, keywords = [] } = blockType;
Expand Down
16 changes: 16 additions & 0 deletions blocks/autocompleters/test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,20 @@ describe( 'blockAutocompleter', () => {
] );
} );
} );

it( 'should omit disallowed block types', () => {
return blockAutocompleter( {
allowedBlockTypes: [ 'core/baz', 'core/bar' ],
} ).getOptions().then( ( options ) => {
expect( options ).toHaveLength( 2 );
expect( options[ 0 ] ).toMatchObject( {
keywords: [ 'baz' ],
value: 'core/baz',
} );
expect( options[ 1 ] ).toMatchObject( {
keywords: [ 'bar' ],
value: 'core/bar',
} );
} );
} );
} );
15 changes: 12 additions & 3 deletions blocks/library/paragraph/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,13 @@ import classnames from 'classnames';
*/
import { __ } from '@wordpress/i18n';
import { concatChildren, Component } from '@wordpress/element';
import { Autocomplete, PanelBody, PanelColor, withFallbackStyles } from '@wordpress/components';
import {
Autocomplete,
PanelBody,
PanelColor,
withFallbackStyles,
withContext,
} from '@wordpress/components';

/**
* Internal dependencies
Expand Down Expand Up @@ -70,6 +76,7 @@ class ParagraphBlock extends Component {
setFocus,
mergeBlocks,
onReplace,
allowedBlockTypes,
} = this.props;

const {
Expand Down Expand Up @@ -142,7 +149,7 @@ class ParagraphBlock extends Component {
),
<div key="editable" ref={ this.bindRef }>
<Autocomplete completers={ [
blockAutocompleter( { onReplace } ),
blockAutocompleter( { onReplace, allowedBlockTypes } ),
userAutocompleter(),
] }>
{ ( { isExpanded, listBoxId, activeId } ) => (
Expand Down Expand Up @@ -265,7 +272,9 @@ export const settings = {
}
},

edit: ParagraphBlock,
edit: withContext( 'editor' )( ( settings ) => ( {
allowedBlockTypes: settings.blockTypes,
} ) )( ParagraphBlock ),

save( { attributes } ) {
const { width, align, content, dropCap, backgroundColor, textColor, fontSize } = attributes;
Expand Down
11 changes: 8 additions & 3 deletions editor/components/block-drop-zone/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* External Dependencies
*/
import { connect } from 'react-redux';
import { reduce, get, find } from 'lodash';
import { reduce, includes, get, find } from 'lodash';

/**
* WordPress dependencies
Expand All @@ -16,7 +16,7 @@ import { compose } from '@wordpress/element';
*/
import { insertBlocks, updateBlockAttributes } from '../../store/actions';

function BlockDropZone( { index, isLocked, ...props } ) {
export function BlockDropZone( { index, isLocked, allowedBlockTypes, ...props } ) {
if ( isLocked ) {
return null;
}
Expand All @@ -29,6 +29,10 @@ function BlockDropZone( { index, isLocked, ...props } ) {

const onDropFiles = ( files, position ) => {
const transformation = reduce( getBlockTypes(), ( ret, blockType ) => {
if ( Array.isArray( allowedBlockTypes ) && ! includes( allowedBlockTypes, blockType.name ) ) {
return ret;
}

if ( ret ) {
return ret;
}
Expand Down Expand Up @@ -67,10 +71,11 @@ export default compose(
{ insertBlocks, updateBlockAttributes }
),
withContext( 'editor' )( ( settings ) => {
const { templateLock } = settings;
const { templateLock, blockTypes } = settings;

return {
isLocked: !! templateLock,
allowedBlockTypes: blockTypes,
};
} )
)( BlockDropZone );
179 changes: 179 additions & 0 deletions editor/components/block-drop-zone/test/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
/**
* External dependencies
*/
import { shallow } from 'enzyme';
import { noop } from 'lodash';

/**
* WordPress dependencies
*/
import {
registerBlockType,
getBlockTypes,
unregisterBlockType,
createBlock,
} from '@wordpress/blocks';

/**
* Internal dependencies
*/
import { BlockDropZone } from '../';

describe( 'BlockDropZone', () => {
beforeAll( () => {
registerBlockType( 'core/foo', {
save: noop,

category: 'common',

title: 'block title',

attributes: {
fileType: {
type: 'string',
},
},

transforms: {
from: [
{
type: 'files',
isMatch: ( files ) => files.some( ( file ) => file.isFooMatch ),
transform( files ) {
return Promise.resolve( files.map( ( file ) => (
createBlock( 'core/foo', { fileType: file.type } )
) ) );
},
},
],
},
} );
} );

afterAll( () => {
getBlockTypes().forEach( block => {
unregisterBlockType( block.name );
} );
} );

it( 'should render nothing if template locking in effect', () => {
const wrapper = shallow( <BlockDropZone isLocked /> );

expect( wrapper.type() ).toBe( null );
} );

it( 'should do nothing if no matched transform for dropped files', ( done ) => {
const insertBlocks = jest.fn();
const wrapper = shallow(
<BlockDropZone insertBlocks={ insertBlocks } />
);

wrapper.prop( 'onFilesDrop' )( [
{
isBarMatch: true,
type: 'application/x-fake',
},
] );

process.nextTick( () => {
expect( insertBlocks ).not.toHaveBeenCalled();
done();
} );
} );

it( 'should call insert callback with transformed files', ( done ) => {
const insertBlocks = jest.fn();
const wrapper = shallow(
<BlockDropZone
index={ 0 }
insertBlocks={ insertBlocks } />
);

wrapper.prop( 'onFilesDrop' )( [
{
isFooMatch: true,
type: 'application/x-fake',
},
], { y: 'bottom' } );

process.nextTick( () => {
expect( insertBlocks ).toHaveBeenCalled();
const [ blocks, index ] = insertBlocks.mock.calls[ 0 ];
expect( blocks ).toHaveLength( 1 );
expect( blocks[ 0 ] ).toMatchObject( {
name: 'core/foo',
attributes: {
fileType: 'application/x-fake',
},
} );
expect( index ).toBe( 1 );
done();
} );
} );

it( 'should call insert callback with transformed files (top)', ( done ) => {
const insertBlocks = jest.fn();
const wrapper = shallow(
<BlockDropZone
index={ 0 }
insertBlocks={ insertBlocks } />
);

wrapper.prop( 'onFilesDrop' )( [
{
isFooMatch: true,
type: 'application/x-fake',
},
], { y: 'top' } );

process.nextTick( () => {
const [ , index ] = insertBlocks.mock.calls[ 0 ];
expect( index ).toBe( 0 );
done();
} );
} );

it( 'should respect allowed block types (not allowed)', ( done ) => {
const insertBlocks = jest.fn();
const wrapper = shallow(
<BlockDropZone
index={ 0 }
allowedBlockTypes={ [ 'core/bar' ] }
insertBlocks={ insertBlocks } />
);

wrapper.prop( 'onFilesDrop' )( [
{
isFooMatch: true,
type: 'application/x-fake',
},
], { y: 'top' } );

process.nextTick( () => {
expect( insertBlocks ).not.toHaveBeenCalled();
done();
} );
} );

it( 'should respect allowed block types (allowed)', ( done ) => {
const insertBlocks = jest.fn();
const wrapper = shallow(
<BlockDropZone
index={ 0 }
allowedBlockTypes={ [ 'core/foo' ] }
insertBlocks={ insertBlocks } />
);

wrapper.prop( 'onFilesDrop' )( [
{
isFooMatch: true,
type: 'application/x-fake',
},
], { y: 'top' } );

process.nextTick( () => {
expect( insertBlocks ).toHaveBeenCalled();
done();
} );
} );
} );