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

Render "normal" metaboxes created by Advanced Custom Fields #2251

Closed
wants to merge 5 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
25 changes: 25 additions & 0 deletions editor/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Provider as ReduxProvider } from 'react-redux';
import { Provider as SlotFillProvider } from 'react-slot-fill';
import moment from 'moment-timezone';
import 'moment-timezone/moment-timezone-utils';
import { partial } from 'lodash';

/**
* WordPress dependencies
Expand Down Expand Up @@ -84,12 +85,32 @@ function preparePostState( store, post ) {
}
}

/**
* Initializes Redux state with metaboxes for the current post.
*
* @param {Redux.Store} store Redux store instance
* @param {Object} metaboxes Metabox data for the current post.
*/
function prepareMetaboxesState( store, metaboxes ) {
if ( ! metaboxes ) {
// eslint-disable-next-line no-console
console.error( 'No data about post metaboxes!' );
return;
}
store.dispatch( {
type: 'RESET_METABOXES',
metaboxes,
} );
}

/**
* Initializes and returns an instance of Editor.
*
* @param {String} id Unique identifier for editor instance
* @param {Object} post API entity for post to edit (type required)
* @param {Object} editorSettings Editor settings object
* @return {Object} Object containing functions that can be
* called to manipulate the editor instance.
*/
export function createEditorInstance( id, post, editorSettings = DEFAULT_SETTINGS ) {
const store = createReduxStore();
Expand Down Expand Up @@ -124,4 +145,8 @@ export function createEditorInstance( id, post, editorSettings = DEFAULT_SETTING
</ReduxProvider>,
document.getElementById( id )
);

return {
setPostMetaboxes: partial( prepareMetaboxesState, store ),
};
}
2 changes: 2 additions & 0 deletions editor/layout/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import TextEditor from '../modes/text-editor';
import VisualEditor from '../modes/visual-editor';
import UnsavedChangesWarning from '../unsaved-changes-warning';
import DocumentTitle from '../document-title';
import NormalMetaboxes from '../metaboxes/normal';
import { removeNotice } from '../actions';
import {
getEditorMode,
Expand All @@ -40,6 +41,7 @@ function Layout( { mode, isSidebarOpened, notices, ...props } ) {
<div className="editor-layout__content">
{ mode === 'text' && <TextEditor /> }
{ mode === 'visual' && <VisualEditor /> }
<NormalMetaboxes />
</div>
{ isSidebarOpened && <Sidebar /> }
</div>
Expand Down
60 changes: 60 additions & 0 deletions editor/metaboxes/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* External dependencies
*/
import { connect } from 'react-redux';
import classnames from 'classnames';

/**
* WordPress dependencies
*/
import { sprintf, __ } from '@wordpress/i18n';
import { Component } from '@wordpress/element';

/**
* Internal dependencies
*/
import './style.scss';

export class Metaboxes extends Component {
render() {
const classes = classnames(
'gutenberg-metaboxes',
'gutenberg-metaboxes__' + this.props.context
);

return (
<div className={ classes }>
<h2>{ this.props.headingText }</h2>
{ this.renderBody() }
</div>
);
}

renderBody() {
const { metaboxes, context } = this.props;
if ( ! metaboxes || ! metaboxes[ context ] ) {
const noMetaboxesMessage =
/* translators: %s: metabox context ('normal', 'advanced', 'side') */
sprintf( __( 'No metaboxes for context: %s' ), context );
return (
<div>
{ noMetaboxesMessage }
</div>
);
}

return (
<div
dangerouslySetInnerHTML={ { __html: metaboxes[ context ] } }
/>
);
}
}

export default connect(
( state ) => {
return {
metaboxes: state.currentPostMetaboxes,
};
}
)( Metaboxes );
18 changes: 18 additions & 0 deletions editor/metaboxes/normal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/**
* WordPress dependencies
*/
import { __ } from 'i18n';

/**
* Internal dependencies
*/
import Metaboxes from './index';

export default function MetaboxesNormal() {
return (
<Metaboxes
context="normal"
headingText={ __( 'Metaboxes: Below Content' ) }
/>
);
}
4 changes: 4 additions & 0 deletions editor/metaboxes/style.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.gutenberg-metaboxes {
margin: 0 auto;
max-width: 700px;
}
17 changes: 17 additions & 0 deletions editor/state.js
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,22 @@ export function currentPost( state = {}, action ) {
return state;
}

/**
* Reducer returning information about the metaboxes for the current post.
*
* @param {Object} state Current state
* @param {Object} action Dispatched action
* @return {Object} Updated state
*/
export function currentPostMetaboxes( state = {}, action ) {
switch ( action.type ) {
case 'RESET_METABOXES':
return action.metaboxes;
}

return state;
}

/**
* Reducer returning selected block state.
*
Expand Down Expand Up @@ -538,6 +554,7 @@ export function createReduxStore() {
const reducer = optimist( combineReducers( {
editor,
currentPost,
currentPostMetaboxes,
selectedBlock,
isTyping,
multiSelectedBlocks,
Expand Down
1 change: 1 addition & 0 deletions gutenberg.php
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
require_once dirname( __FILE__ ) . '/lib/init-checks.php';
if ( gutenberg_can_init() ) {
// Load API functions, register scripts and actions, etc.
require_once dirname( __FILE__ ) . '/lib/api/class-gutenberg-metaboxes-controller.php';
require_once dirname( __FILE__ ) . '/lib/class-wp-block-type.php';
require_once dirname( __FILE__ ) . '/lib/class-wp-block-type-registry.php';
require_once dirname( __FILE__ ) . '/lib/blocks.php';
Expand Down
163 changes: 163 additions & 0 deletions lib/api/class-gutenberg-metaboxes-controller.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
<?php
/**
* REST API endpoint to render and (eventually) update metabox content.
*
* @package gutenberg
*/

if ( ! defined( 'ABSPATH' ) ) {
die( 'Silence is golden.' );
}

/**
* Controller class for metaboxes API endpoint.
*/
class Gutenberg_Metaboxes_Controller {
/**
* Register REST API routes.
*/
public static function register_routes() {
register_rest_route( 'gutenberg/v1', '/metaboxes', array(
array(
'methods' => WP_REST_Server::READABLE,
'permission_callback' => array( __CLASS__, 'get_items_permissions_check' ),
'callback' => array( __CLASS__, 'get_items' ),
'args' => array(
'post_id' => array(
'description' => __(
'The ID of the post for which to fetch metaboxes.',
'gutenberg'
),
'type' => 'integer',
),
),
),
) );
}

/**
* Check permissions for reading metaboxes. Requires `edit_post` for the
* requested post.
*
* @param WP_REST_Request $request Full details about the request.
* @return bool|WP_Error True if the request has read access for the item, WP_Error object otherwise.
*/
public static function get_items_permissions_check( $request ) {
$post = get_post( $request['post_id'] );
if ( ! $post ) {
return new WP_Error(
'rest_post_invalid_id',
__( 'Invalid post ID.', 'gutenberg' ),
array(
'status' => 404,
)
);
}

$post_type = get_post_type_object( $post->post_type );
$allowed_post_types = get_post_types( array(
'show_in_rest' => true,
) );
if ( ! in_array( $post_type->name, $allowed_post_types, true ) ) {
return new WP_Error(
'rest_post_invalid_type',
__( 'Invalid post type.', 'gutenberg' ),
array(
'status' => 404,
)
);
}

if ( current_user_can( $post_type->cap->edit_post, $post->ID ) ) {
return true;
}

return false;
}

/**
* Returns information about metaboxes for the requested post.
*
* Plugins that add metaboxes along with their timing:
*
* advanced-custom-fields
* ======================
* An 'admin_enqueue_scripts' callback (priority 10) verifies that `$pagenow`
* is `post.php` or `post-new.php`, then adds an 'admin_head' callback which
* calls add_meta_box().
*
* @param WP_REST_Request $request Full details about the request.
* @return array Information about metaboxes for the requested post.
*/
public static function get_items( $request ) {
$post = get_post( $request['post_id'] );
$output = array();

// advanced-custom-fields doesn't do anything unless is_admin().
define( 'WP_ADMIN', true );

// advanced-custom-fields doesn't register metaboxes without these.
$GLOBALS['pagenow'] = 'post.php';
$GLOBALS['typenow'] = $post->post_type;

// Load wp-admin APIs and functions. This shouldn't produce any
// output, but we can collect it just in case and verify during
// testing.
ob_start();
require_once ABSPATH . 'wp-admin/includes/admin.php';
$output['include_admin'] = ob_get_clean();

// Capture output produced by the following actions.
// TODO: Collect more specific info about enqueued assets.
ob_start();

/*
* Normally called via wp-admin/post.php ->
* wp-admin/edit-form-advanced.php -> wp-admin/admin-header.php
*/
do_action( 'admin_enqueue_scripts', 'post.php' );
do_action( 'admin_print_styles-post.php' );
do_action( 'admin_print_styles' );
do_action( 'admin_print_scripts-post.php' );
do_action( 'admin_print_scripts' );
do_action( 'admin_head-post.php' );
do_action( 'admin_head' );

$output['admin_head'] = ob_get_clean();

/*
* Adapted from code in wp-admin/edit-form-advanced.php:
* do_action( 'do_meta_boxes', $post_type, {'normal','advanced','side'}, $post );
* do_meta_boxes( $post_type, 'side', $post );
* do_meta_boxes( null, 'normal', $post );
* do_meta_boxes( null, 'advanced', $post );
*/
foreach ( array( 'normal', 'advanced', 'side' ) as $location ) {
ob_start();
do_action(
'do_meta_boxes',
$post->post_type,
$location,
$post
);
do_meta_boxes(
'side' === $location ? $post->post_type : null,
$location,
$post
);
$output[ $location ] = ob_get_clean();
}

// TODO wp-admin/post.php -> wp-admin/admin-footer.php ?
; // Just for you, phpcs.

return array(
'html' => $output,
);
}
}

add_action(
'rest_api_init',
array( 'Gutenberg_Metaboxes_Controller', 'register_routes' )
);
14 changes: 11 additions & 3 deletions lib/client-assets.php
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,7 @@ function gutenberg_editor_scripts_and_styles( $hook ) {
if ( is_wp_error( $post_to_edit ) ) {
wp_die( $post_to_edit->get_error_message() );
}
$GLOBALS['_gutenberg_post_id'] = $post_id; // TODO something other than this.

// Set initial title to empty string for auto draft for duration of edit.
$is_new_post = 'auto-draft' === $post_to_edit['status'];
Expand Down Expand Up @@ -661,10 +662,17 @@ function gutenberg_editor_scripts_and_styles( $hook ) {
'colors' => $color_palette,
);

wp_add_inline_script( 'wp-editor', 'wp.api.init().done( function() {'
. 'wp.editor.createEditorInstance( \'editor\', window._wpGutenbergPost, ' . json_encode( $editor_settings ) . ' ); '
. '} );'
$editor_settings_encoded = json_encode( $editor_settings );
$create_editor_instance_script = <<<JS
wp.api.init().done( function() {
window._wpGutenbergInstance = wp.editor.createEditorInstance(
'editor',
window._wpGutenbergPost,
$editor_settings_encoded
);
} );
JS;
wp_add_inline_script( 'wp-editor', $create_editor_instance_script );

/**
* Scripts
Expand Down
Loading