From 5908898ed36ea6c5c3c4ecca2f4844b26a41133a Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Tue, 19 Nov 2019 11:26:32 -0600 Subject: [PATCH 01/63] Initial scaffolding for Eventbrite block --- extensions/blocks/eventbrite/edit.js | 12 ++++++ extensions/blocks/eventbrite/editor.js | 7 +++ extensions/blocks/eventbrite/eventbrite.php | 27 ++++++++++++ extensions/blocks/eventbrite/index.js | 47 +++++++++++++++++++++ extensions/index.json | 1 + 5 files changed, 94 insertions(+) create mode 100644 extensions/blocks/eventbrite/edit.js create mode 100644 extensions/blocks/eventbrite/editor.js create mode 100644 extensions/blocks/eventbrite/eventbrite.php create mode 100644 extensions/blocks/eventbrite/index.js diff --git a/extensions/blocks/eventbrite/edit.js b/extensions/blocks/eventbrite/edit.js new file mode 100644 index 0000000000000..9cffe5bbf3973 --- /dev/null +++ b/extensions/blocks/eventbrite/edit.js @@ -0,0 +1,12 @@ +/** + * External dependencies + */ +import { Component } from '@wordpress/element'; + +class EventbriteEdit extends Component { + render() { + return 'HELLO WORLD!'; + } +} + +export default EventbriteEdit; diff --git a/extensions/blocks/eventbrite/editor.js b/extensions/blocks/eventbrite/editor.js new file mode 100644 index 0000000000000..d05f403942058 --- /dev/null +++ b/extensions/blocks/eventbrite/editor.js @@ -0,0 +1,7 @@ +/** + * Internal dependencies + */ +import registerJetpackBlock from '../../shared/register-jetpack-block'; +import { name, settings } from '.'; + +registerJetpackBlock( name, settings ); diff --git a/extensions/blocks/eventbrite/eventbrite.php b/extensions/blocks/eventbrite/eventbrite.php new file mode 100644 index 0000000000000..e1449979f9d38 --- /dev/null +++ b/extensions/blocks/eventbrite/eventbrite.php @@ -0,0 +1,27 @@ + 'jetpack_eventbrite_block_load_assets' ) +); + +/** + * Eventbrite block registration/dependency delclaration. + * + * @param array $attr Eventbrite block attributes. + * @param string $content Eventbrite block content. + * + * @return string + */ +function jetpack_eventbrite_block_load_assets( $attr, $content ) { + wp_enqueue_script( 'eventbrite-widget', 'https://www.eventbrite.com/static/widgets/eb_widgets.js', array(), JETPACK__VERSION, true ); + + return $content; +} diff --git a/extensions/blocks/eventbrite/index.js b/extensions/blocks/eventbrite/index.js new file mode 100644 index 0000000000000..3ca6931b9c6a5 --- /dev/null +++ b/extensions/blocks/eventbrite/index.js @@ -0,0 +1,47 @@ +/** + * External dependencies + */ +import { __ } from '@wordpress/i18n'; + +/** + * Internal dependencies + */ +import edit from './edit'; + +export const name = 'eventbrite'; + +// Should this be 'Eventbrite Tickets', since we may add other embeds in the future? +export const title = __( 'Eventbrite', 'jetpack' ); + +export const icon = null; + +export const settings = { + title, + + description: __( 'Embed Eventbrite event details and ticket checkout.', 'jetpack' ), + + // icon, + + category: 'jetpack', + + supports: { + html: false, + }, + + attributes: { + eventId: { + type: 'string', + }, + useModal: { + type: 'boolean', + }, + }, + + edit, + + save: () => { + // const { eventId, useModal } = attributes; + + return
Event!
; + }, +}; diff --git a/extensions/index.json b/extensions/index.json index 551d29858d682..095006b6e042c 100644 --- a/extensions/index.json +++ b/extensions/index.json @@ -3,6 +3,7 @@ "business-hours", "contact-form", "contact-info", + "eventbrite", "gif", "likes", "mailchimp", From 515534bc677f9bb8f56b44c38b8c36231b6f1cc1 Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Tue, 19 Nov 2019 14:47:24 -0600 Subject: [PATCH 02/63] Request Eventbrite events when block is inserted --- _inc/lib/class.core-rest-api-endpoints.php | 24 ++++++--- ...k-core-api-integrations-proxy-endpoint.php | 54 +++++++++++++++++++ extensions/blocks/eventbrite/edit.js | 7 +++ 3 files changed, 79 insertions(+), 6 deletions(-) create mode 100644 _inc/lib/core-api/class.jetpack-core-api-integrations-proxy-endpoint.php diff --git a/_inc/lib/class.core-rest-api-endpoints.php b/_inc/lib/class.core-rest-api-endpoints.php index 742767b08f87d..6e2d4a7b588c9 100644 --- a/_inc/lib/class.core-rest-api-endpoints.php +++ b/_inc/lib/class.core-rest-api-endpoints.php @@ -56,6 +56,7 @@ public static function register_endpoints() { require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php'; // Load API endpoints + require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-integrations-proxy-endpoint.php'; require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php'; require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php'; require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-widgets-endpoints.php'; @@ -68,13 +69,14 @@ public static function register_endpoints() { self::$stats_roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' ); - $ixr_client = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) ); - $core_api_endpoint = new Jetpack_Core_API_Data( $ixr_client ); - $module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint(); - $module_data_endpoint = new Jetpack_Core_API_Module_Data_Endpoint(); + $ixr_client = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) ); + $core_api_endpoint = new Jetpack_Core_API_Data( $ixr_client ); + $module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint(); + $module_data_endpoint = new Jetpack_Core_API_Module_Data_Endpoint(); $module_toggle_endpoint = new Jetpack_Core_API_Module_Toggle_Endpoint( new Jetpack_IXR_Client() ); - $site_endpoint = new Jetpack_Core_API_Site_Endpoint(); - $widget_endpoint = new Jetpack_Core_API_Widget_Endpoint(); + $site_endpoint = new Jetpack_Core_API_Site_Endpoint(); + $widget_endpoint = new Jetpack_Core_API_Widget_Endpoint(); + $integrations_endpoint = new Jetpack_Core_API_Integrations_Proxy(); register_rest_route( 'jetpack/v4', 'plans', array( 'methods' => WP_REST_Server::READABLE, @@ -494,6 +496,16 @@ public static function register_endpoints() { 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', ) ); + + register_rest_route( + 'jetpack/v4', + 'integrations/(?P.+)', + array( + 'methods' => WP_REST_Server::ALLMETHODS, + 'callback' => array( $integrations_endpoint, 'wpcom_request_as_user' ), + 'permission_callback' => array( $integrations_endpoint, 'user_can_proxy_request' ), + ) + ); } public static function get_plans( $request ) { diff --git a/_inc/lib/core-api/class.jetpack-core-api-integrations-proxy-endpoint.php b/_inc/lib/core-api/class.jetpack-core-api-integrations-proxy-endpoint.php new file mode 100644 index 0000000000000..35f6429f0d5a8 --- /dev/null +++ b/_inc/lib/core-api/class.jetpack-core-api-integrations-proxy-endpoint.php @@ -0,0 +1,54 @@ +get_param( 'path' ); + if ( ! $integrations_path ) { + return new WP_Error( 'rest_invalid_path' ); + } + + $response = Client::wpcom_json_api_request_as_user( + sprintf( '/sites/%d/integrations/%s', $site_id, $integrations_path ), + '2', + array(), + null, + 'wpcom' + ); + + return is_wp_error( $response ) + ? $response + : rest_ensure_response( json_decode( wp_remote_retrieve_body( $response ) ) ); + } + + /** + * Permissions check for proxied requests. + * + * Each /integrations endpoint on wpcom should do it's own, more granular, checks. + */ + public function user_can_proxy_request() { + return is_user_logged_in(); + } +} diff --git a/extensions/blocks/eventbrite/edit.js b/extensions/blocks/eventbrite/edit.js index 9cffe5bbf3973..0750d7e5e145f 100644 --- a/extensions/blocks/eventbrite/edit.js +++ b/extensions/blocks/eventbrite/edit.js @@ -2,8 +2,15 @@ * External dependencies */ import { Component } from '@wordpress/element'; +import apiFetch from '@wordpress/api-fetch'; class EventbriteEdit extends Component { + componentDidMount = () => { + apiFetch( { + path: '/jetpack/v4/integrations/eventbrite', + } ); + }; + render() { return 'HELLO WORLD!'; } From e03176649847a6ea8358ccea06bbc0be33353e95 Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Tue, 19 Nov 2019 15:28:51 -0600 Subject: [PATCH 03/63] Displays select dropdown of events from Eventbrite --- extensions/blocks/eventbrite/edit.js | 37 +++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/extensions/blocks/eventbrite/edit.js b/extensions/blocks/eventbrite/edit.js index 0750d7e5e145f..0ade4f6f88213 100644 --- a/extensions/blocks/eventbrite/edit.js +++ b/extensions/blocks/eventbrite/edit.js @@ -1,18 +1,49 @@ /** * External dependencies */ +import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import apiFetch from '@wordpress/api-fetch'; +import { SelectControl } from '@wordpress/components'; + +const defaultEvent = { label: __( 'Select event', 'jetpack' ), value: '' }; class EventbriteEdit extends Component { - componentDidMount = () => { - apiFetch( { + state = { + events: [], + selectedEventID: null, + }; + + componentDidMount() { + this.getEvents(); + } + + getEvents = async () => { + const response = await apiFetch( { path: '/jetpack/v4/integrations/eventbrite', } ); + + const events = response.events.map( event => ( { + label: event.post_title, + value: event.ID, + } ) ); + + this.setState( { events } ); }; render() { - return 'HELLO WORLD!'; + const { events, selectedEventID } = this.state; + + return ( + { + this.setState( { selectedEventID: eventID } ); + } } + /> + ); } } From e0746be26969168996641f5d493ca22434d2d75f Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Tue, 19 Nov 2019 18:06:25 -0600 Subject: [PATCH 04/63] Saves event id as block attribute --- extensions/blocks/eventbrite/edit.js | 13 +++++++------ extensions/blocks/eventbrite/index.js | 24 ++++++++++++++++++++---- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/extensions/blocks/eventbrite/edit.js b/extensions/blocks/eventbrite/edit.js index 0ade4f6f88213..25c72c2315b21 100644 --- a/extensions/blocks/eventbrite/edit.js +++ b/extensions/blocks/eventbrite/edit.js @@ -11,7 +11,6 @@ const defaultEvent = { label: __( 'Select event', 'jetpack' ), value: '' }; class EventbriteEdit extends Component { state = { events: [], - selectedEventID: null, }; componentDidMount() { @@ -31,17 +30,19 @@ class EventbriteEdit extends Component { this.setState( { events } ); }; + setEvent = eventId => { + this.props.setAttributes( { eventId: eventId } ); + }; + render() { - const { events, selectedEventID } = this.state; + const { events } = this.state; return ( { - this.setState( { selectedEventID: eventID } ); - } } + onChange={ this.setEvent } /> ); } diff --git a/extensions/blocks/eventbrite/index.js b/extensions/blocks/eventbrite/index.js index 3ca6931b9c6a5..e9649350ea624 100644 --- a/extensions/blocks/eventbrite/index.js +++ b/extensions/blocks/eventbrite/index.js @@ -39,9 +39,25 @@ export const settings = { edit, - save: () => { - // const { eventId, useModal } = attributes; - - return
Event!
; + save: ( { attributes } ) => { + const { eventId } = attributes; + const html = ` + + + + + `; + + /* eslint-disable */ + return
; }, }; From 3eeeb5fcb42f53a405bf754214f74083a8eec5e2 Mon Sep 17 00:00:00 2001 From: Gary Pendergast Date: Wed, 20 Nov 2019 06:11:19 +0000 Subject: [PATCH 05/63] Render the Eventbrite widget code in PHP, rather than saving it in the block. --- extensions/blocks/eventbrite/eventbrite.php | 29 +++++++++++++++++---- extensions/blocks/eventbrite/index.js | 22 +--------------- 2 files changed, 25 insertions(+), 26 deletions(-) diff --git a/extensions/blocks/eventbrite/eventbrite.php b/extensions/blocks/eventbrite/eventbrite.php index e1449979f9d38..6b422304bf9af 100644 --- a/extensions/blocks/eventbrite/eventbrite.php +++ b/extensions/blocks/eventbrite/eventbrite.php @@ -9,19 +9,38 @@ jetpack_register_block( 'jetpack/eventbrite', - array( 'render_callback' => 'jetpack_eventbrite_block_load_assets' ) + array( + 'attributes' => array( + 'eventId' => array( + 'type' => 'string', + ), + ), + 'render_callback' => 'jetpack_eventbrite_block_load_assets', + ) ); /** * Eventbrite block registration/dependency delclaration. * - * @param array $attr Eventbrite block attributes. - * @param string $content Eventbrite block content. + * @param array $attr Eventbrite block attributes. * * @return string */ -function jetpack_eventbrite_block_load_assets( $attr, $content ) { +function jetpack_eventbrite_block_load_assets( $attr ) { wp_enqueue_script( 'eventbrite-widget', 'https://www.eventbrite.com/static/widgets/eb_widgets.js', array(), JETPACK__VERSION, true ); + wp_add_inline_script( + 'eventbrite-widget', + "window.EBWidgets.createWidget({ + widgetType: 'checkout', + eventId: ${attr['eventId']}, + iframeContainerId: 'eventbrite-widget-container-${attr['eventId']}', + });" + ); - return $content; + return <<
+ +EOT; } diff --git a/extensions/blocks/eventbrite/index.js b/extensions/blocks/eventbrite/index.js index e9649350ea624..7fdc769efbff6 100644 --- a/extensions/blocks/eventbrite/index.js +++ b/extensions/blocks/eventbrite/index.js @@ -39,25 +39,5 @@ export const settings = { edit, - save: ( { attributes } ) => { - const { eventId } = attributes; - const html = ` - - - - - `; - - /* eslint-disable */ - return
; - }, + save: () => null, }; From 686c3bf15bc3bcac645971b2d743c1145519a877 Mon Sep 17 00:00:00 2001 From: Gary Pendergast Date: Thu, 21 Nov 2019 05:23:48 +0000 Subject: [PATCH 06/63] Add the new endpoint file the the phpcs whitelist. --- bin/phpcs-whitelist.js | 1 + extensions/blocks/eventbrite/eventbrite.php | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/bin/phpcs-whitelist.js b/bin/phpcs-whitelist.js index 3bf328fa5353b..3050a6ecf781e 100644 --- a/bin/phpcs-whitelist.js +++ b/bin/phpcs-whitelist.js @@ -12,6 +12,7 @@ module.exports = [ '_inc/lib/admin-pages/class-jetpack-about-page.php', '_inc/lib/class.jetpack-password-checker.php', '_inc/lib/components.php', + '_inc/lib/core-api/class.jetpack-core-api-integrations-proxy-endpoint.php', '_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php', '_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-resolve-redirect.php', '_inc/lib/core-api/wpcom-endpoints/memberships.php', diff --git a/extensions/blocks/eventbrite/eventbrite.php b/extensions/blocks/eventbrite/eventbrite.php index 6b422304bf9af..1771720aa3cf7 100644 --- a/extensions/blocks/eventbrite/eventbrite.php +++ b/extensions/blocks/eventbrite/eventbrite.php @@ -27,6 +27,10 @@ * @return string */ function jetpack_eventbrite_block_load_assets( $attr ) { + if ( empty( $attr['eventId'] ) ) { + return ''; + } + wp_enqueue_script( 'eventbrite-widget', 'https://www.eventbrite.com/static/widgets/eb_widgets.js', array(), JETPACK__VERSION, true ); wp_add_inline_script( 'eventbrite-widget', From 8737182d8f59eb8e4bd50a9de777f97a251b2c1a Mon Sep 17 00:00:00 2001 From: Gary Pendergast Date: Thu, 21 Nov 2019 05:41:12 +0000 Subject: [PATCH 07/63] Add a spinner while loading events --- extensions/blocks/eventbrite/edit.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/extensions/blocks/eventbrite/edit.js b/extensions/blocks/eventbrite/edit.js index 25c72c2315b21..c3c88d10566d8 100644 --- a/extensions/blocks/eventbrite/edit.js +++ b/extensions/blocks/eventbrite/edit.js @@ -4,12 +4,13 @@ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import apiFetch from '@wordpress/api-fetch'; -import { SelectControl } from '@wordpress/components'; +import { SelectControl, Spinner } from '@wordpress/components'; const defaultEvent = { label: __( 'Select event', 'jetpack' ), value: '' }; class EventbriteEdit extends Component { state = { + fetchingEvents: true, events: [], }; @@ -27,7 +28,7 @@ class EventbriteEdit extends Component { value: event.ID, } ) ); - this.setState( { events } ); + this.setState( { events, fetchingEvents: false } ); }; setEvent = eventId => { @@ -35,7 +36,16 @@ class EventbriteEdit extends Component { }; render() { - const { events } = this.state; + const { events, fetchingEvents } = this.state; + + if ( fetchingEvents ) { + return ( +
+ +

{ __( 'Loading events…', 'jetpack' ) }

+
+ ); + } return ( Date: Thu, 21 Nov 2019 05:57:39 +0000 Subject: [PATCH 08/63] Add an option for switching between inline and modal embeds --- extensions/blocks/eventbrite/edit.js | 36 +++++++++++++++++---- extensions/blocks/eventbrite/eventbrite.php | 36 +++++++++++++++++---- 2 files changed, 59 insertions(+), 13 deletions(-) diff --git a/extensions/blocks/eventbrite/edit.js b/extensions/blocks/eventbrite/edit.js index c3c88d10566d8..cfe7dae84894f 100644 --- a/extensions/blocks/eventbrite/edit.js +++ b/extensions/blocks/eventbrite/edit.js @@ -4,7 +4,8 @@ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import apiFetch from '@wordpress/api-fetch'; -import { SelectControl, Spinner } from '@wordpress/components'; +import { PanelBody, RadioControl, SelectControl, Spinner } from '@wordpress/components'; +import { InspectorControls } from '@wordpress/editor'; const defaultEvent = { label: __( 'Select event', 'jetpack' ), value: '' }; @@ -36,6 +37,7 @@ class EventbriteEdit extends Component { }; render() { + const { eventId, useModal } = this.props.attributes; const { events, fetchingEvents } = this.state; if ( fetchingEvents ) { @@ -48,12 +50,32 @@ class EventbriteEdit extends Component { } return ( - +
+ + + this.props.setAttributes( { useModal: 'modal' === option } ) } + /> + + + + +
); } } diff --git a/extensions/blocks/eventbrite/eventbrite.php b/extensions/blocks/eventbrite/eventbrite.php index 1771720aa3cf7..202f3db890363 100644 --- a/extensions/blocks/eventbrite/eventbrite.php +++ b/extensions/blocks/eventbrite/eventbrite.php @@ -11,9 +11,12 @@ 'jetpack/eventbrite', array( 'attributes' => array( - 'eventId' => array( + 'eventId' => array( 'type' => 'string', ), + 'useModal' => array( + 'type' => 'boolean', + ), ), 'render_callback' => 'jetpack_eventbrite_block_load_assets', ) @@ -32,19 +35,40 @@ function jetpack_eventbrite_block_load_assets( $attr ) { } wp_enqueue_script( 'eventbrite-widget', 'https://www.eventbrite.com/static/widgets/eb_widgets.js', array(), JETPACK__VERSION, true ); + + // Show the embedded version. + if ( empty( $attr['useModal'] ) ) { + wp_add_inline_script( + 'eventbrite-widget', + "window.EBWidgets.createWidget({ + widgetType: 'checkout', + eventId: ${attr['eventId']}, + iframeContainerId: 'eventbrite-widget-container-${attr['eventId']}', + });" + ); + + return <<
+ +EOT; + } + + // Show the modal version. wp_add_inline_script( 'eventbrite-widget', "window.EBWidgets.createWidget({ widgetType: 'checkout', eventId: ${attr['eventId']}, - iframeContainerId: 'eventbrite-widget-container-${attr['eventId']}', + modal: true, + modalTriggerElementId: 'eventbrite-widget-modal-trigger-${attr['eventId']}', });" ); return << - + + + EOT; } From f5863883f6bc9365c26fc151304eff2c6e480013 Mon Sep 17 00:00:00 2001 From: Gary Date: Fri, 22 Nov 2019 16:46:23 +1100 Subject: [PATCH 09/63] Don't load the block if Jetpack isn't connected. --- extensions/blocks/eventbrite/eventbrite.php | 28 +++++++++++---------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/extensions/blocks/eventbrite/eventbrite.php b/extensions/blocks/eventbrite/eventbrite.php index 202f3db890363..e84302d238b81 100644 --- a/extensions/blocks/eventbrite/eventbrite.php +++ b/extensions/blocks/eventbrite/eventbrite.php @@ -7,20 +7,22 @@ * @package Jetpack */ -jetpack_register_block( - 'jetpack/eventbrite', - array( - 'attributes' => array( - 'eventId' => array( - 'type' => 'string', +if ( ( defined( 'IS_WPCOM' ) && IS_WPCOM ) || Jetpack::is_active() ) { + jetpack_register_block( + 'jetpack/eventbrite', + array( + 'attributes' => array( + 'eventId' => array( + 'type' => 'string', + ), + 'useModal' => array( + 'type' => 'boolean', + ), ), - 'useModal' => array( - 'type' => 'boolean', - ), - ), - 'render_callback' => 'jetpack_eventbrite_block_load_assets', - ) -); + 'render_callback' => 'jetpack_eventbrite_block_load_assets', + ) + ); +} /** * Eventbrite block registration/dependency delclaration. From c3536a6ccb575b84f7ed76517a33afe4e6a31f40 Mon Sep 17 00:00:00 2001 From: Gary Pendergast Date: Fri, 22 Nov 2019 06:48:24 +0000 Subject: [PATCH 10/63] Add a (currently not building) connect button. --- ...k-core-api-integrations-proxy-endpoint.php | 13 ++++-- extensions/blocks/eventbrite/edit.js | 43 +++++++++++++++++-- 2 files changed, 50 insertions(+), 6 deletions(-) diff --git a/_inc/lib/core-api/class.jetpack-core-api-integrations-proxy-endpoint.php b/_inc/lib/core-api/class.jetpack-core-api-integrations-proxy-endpoint.php index 35f6429f0d5a8..f9a7fdc2697ba 100644 --- a/_inc/lib/core-api/class.jetpack-core-api-integrations-proxy-endpoint.php +++ b/_inc/lib/core-api/class.jetpack-core-api-integrations-proxy-endpoint.php @@ -38,9 +38,16 @@ public function wpcom_request_as_user( WP_REST_Request $request ) { 'wpcom' ); - return is_wp_error( $response ) - ? $response - : rest_ensure_response( json_decode( wp_remote_retrieve_body( $response ) ) ); + if ( is_wp_error( $response ) ) { + return $response; + } + + $response_data = json_decode( wp_remote_retrieve_body( $response ) ); + if ( is_string( $response_data ) ) { + $response_data = array( 'connect_url' => $response_data ); + } + + return rest_ensure_response( $response_data ); } /** diff --git a/extensions/blocks/eventbrite/edit.js b/extensions/blocks/eventbrite/edit.js index cfe7dae84894f..88eda20a2bfe1 100644 --- a/extensions/blocks/eventbrite/edit.js +++ b/extensions/blocks/eventbrite/edit.js @@ -4,14 +4,20 @@ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import apiFetch from '@wordpress/api-fetch'; -import { PanelBody, RadioControl, SelectControl, Spinner } from '@wordpress/components'; +import { PanelBody, RadioControl, SelectControl, Spinner, Button } from '@wordpress/components'; import { InspectorControls } from '@wordpress/editor'; +/** + * Internal dependences + */ +import requestExternalAccess from 'lib/sharing'; + const defaultEvent = { label: __( 'Select event', 'jetpack' ), value: '' }; class EventbriteEdit extends Component { state = { fetchingEvents: true, + connectUrl: '', events: [], }; @@ -24,12 +30,25 @@ class EventbriteEdit extends Component { path: '/jetpack/v4/integrations/eventbrite', } ); + // If the API returned a connection URL, save it and retry a little later. + if ( response.connect_url ) { + this.setState( { + connectUrl: response.connect_url, + } ); + return; + } + + // Save the events returned. const events = response.events.map( event => ( { label: event.post_title, value: event.ID, } ) ); - this.setState( { events, fetchingEvents: false } ); + this.setState( { + connectUrl: '', + events, + fetchingEvents: false, + } ); }; setEvent = eventId => { @@ -38,7 +57,25 @@ class EventbriteEdit extends Component { render() { const { eventId, useModal } = this.props.attributes; - const { events, fetchingEvents } = this.state; + const { events, fetchingEvents, connectUrl } = this.state; + + if ( connectUrl ) { + return ( +
+ +
+ ); + } if ( fetchingEvents ) { return ( From dba113261eb21315521280928ca0a1d84bc00d1f Mon Sep 17 00:00:00 2001 From: Gary Pendergast Date: Fri, 22 Nov 2019 06:59:37 +0000 Subject: [PATCH 11/63] Update webpack config to include _inc/client in blocks --- webpack.config.extensions.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/webpack.config.extensions.js b/webpack.config.extensions.js index 704c058de32d1..b8184f0b52d11 100644 --- a/webpack.config.extensions.js +++ b/webpack.config.extensions.js @@ -113,6 +113,10 @@ module.exports = [ ..._.without( extensionsWebpackConfig.module.rules, transpileConfig ), ], }, + resolve: { + ...extensionsWebpackConfig.resolve, + modules: [ path.resolve( __dirname, '_inc/client' ), 'node_modules' ], + }, plugins: [ ...extensionsWebpackConfig.plugins, new CopyWebpackPlugin( [ From aa8a19481a664b82ce883ec4bda7dd2508ebb128 Mon Sep 17 00:00:00 2001 From: Gary Pendergast Date: Fri, 22 Nov 2019 11:31:40 +0000 Subject: [PATCH 12/63] Don't open a new Eventbrite connect dialog every time the block renders. --- extensions/blocks/eventbrite/edit.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/extensions/blocks/eventbrite/edit.js b/extensions/blocks/eventbrite/edit.js index 88eda20a2bfe1..9363897b5fb66 100644 --- a/extensions/blocks/eventbrite/edit.js +++ b/extensions/blocks/eventbrite/edit.js @@ -64,12 +64,14 @@ class EventbriteEdit extends Component {
From 7e5ca9810cbf742e812056c28d696c11fe4d5722 Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Thu, 12 Dec 2019 17:06:35 -0600 Subject: [PATCH 13/63] Removes integrations proxy endpoint (unneeded with simple embed) --- _inc/lib/class.core-rest-api-endpoints.php | 24 ++------ ...k-core-api-integrations-proxy-endpoint.php | 61 ------------------- bin/phpcs-whitelist.js | 1 - 3 files changed, 6 insertions(+), 80 deletions(-) delete mode 100644 _inc/lib/core-api/class.jetpack-core-api-integrations-proxy-endpoint.php diff --git a/_inc/lib/class.core-rest-api-endpoints.php b/_inc/lib/class.core-rest-api-endpoints.php index 6e2d4a7b588c9..742767b08f87d 100644 --- a/_inc/lib/class.core-rest-api-endpoints.php +++ b/_inc/lib/class.core-rest-api-endpoints.php @@ -56,7 +56,6 @@ public static function register_endpoints() { require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-xmlrpc-consumer-endpoint.php'; // Load API endpoints - require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-integrations-proxy-endpoint.php'; require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-module-endpoints.php'; require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php'; require_once JETPACK__PLUGIN_DIR . '_inc/lib/core-api/class.jetpack-core-api-widgets-endpoints.php'; @@ -69,14 +68,13 @@ public static function register_endpoints() { self::$stats_roles = array( 'administrator', 'editor', 'author', 'contributor', 'subscriber' ); - $ixr_client = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) ); - $core_api_endpoint = new Jetpack_Core_API_Data( $ixr_client ); - $module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint(); - $module_data_endpoint = new Jetpack_Core_API_Module_Data_Endpoint(); + $ixr_client = new Jetpack_IXR_Client( array( 'user_id' => get_current_user_id() ) ); + $core_api_endpoint = new Jetpack_Core_API_Data( $ixr_client ); + $module_list_endpoint = new Jetpack_Core_API_Module_List_Endpoint(); + $module_data_endpoint = new Jetpack_Core_API_Module_Data_Endpoint(); $module_toggle_endpoint = new Jetpack_Core_API_Module_Toggle_Endpoint( new Jetpack_IXR_Client() ); - $site_endpoint = new Jetpack_Core_API_Site_Endpoint(); - $widget_endpoint = new Jetpack_Core_API_Widget_Endpoint(); - $integrations_endpoint = new Jetpack_Core_API_Integrations_Proxy(); + $site_endpoint = new Jetpack_Core_API_Site_Endpoint(); + $widget_endpoint = new Jetpack_Core_API_Widget_Endpoint(); register_rest_route( 'jetpack/v4', 'plans', array( 'methods' => WP_REST_Server::READABLE, @@ -496,16 +494,6 @@ public static function register_endpoints() { 'permission_callback' => __CLASS__ . '::view_admin_page_permission_check', ) ); - - register_rest_route( - 'jetpack/v4', - 'integrations/(?P.+)', - array( - 'methods' => WP_REST_Server::ALLMETHODS, - 'callback' => array( $integrations_endpoint, 'wpcom_request_as_user' ), - 'permission_callback' => array( $integrations_endpoint, 'user_can_proxy_request' ), - ) - ); } public static function get_plans( $request ) { diff --git a/_inc/lib/core-api/class.jetpack-core-api-integrations-proxy-endpoint.php b/_inc/lib/core-api/class.jetpack-core-api-integrations-proxy-endpoint.php deleted file mode 100644 index f9a7fdc2697ba..0000000000000 --- a/_inc/lib/core-api/class.jetpack-core-api-integrations-proxy-endpoint.php +++ /dev/null @@ -1,61 +0,0 @@ -get_param( 'path' ); - if ( ! $integrations_path ) { - return new WP_Error( 'rest_invalid_path' ); - } - - $response = Client::wpcom_json_api_request_as_user( - sprintf( '/sites/%d/integrations/%s', $site_id, $integrations_path ), - '2', - array(), - null, - 'wpcom' - ); - - if ( is_wp_error( $response ) ) { - return $response; - } - - $response_data = json_decode( wp_remote_retrieve_body( $response ) ); - if ( is_string( $response_data ) ) { - $response_data = array( 'connect_url' => $response_data ); - } - - return rest_ensure_response( $response_data ); - } - - /** - * Permissions check for proxied requests. - * - * Each /integrations endpoint on wpcom should do it's own, more granular, checks. - */ - public function user_can_proxy_request() { - return is_user_logged_in(); - } -} diff --git a/bin/phpcs-whitelist.js b/bin/phpcs-whitelist.js index 3050a6ecf781e..3bf328fa5353b 100644 --- a/bin/phpcs-whitelist.js +++ b/bin/phpcs-whitelist.js @@ -12,7 +12,6 @@ module.exports = [ '_inc/lib/admin-pages/class-jetpack-about-page.php', '_inc/lib/class.jetpack-password-checker.php', '_inc/lib/components.php', - '_inc/lib/core-api/class.jetpack-core-api-integrations-proxy-endpoint.php', '_inc/lib/core-api/class.jetpack-core-api-site-endpoints.php', '_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-resolve-redirect.php', '_inc/lib/core-api/wpcom-endpoints/memberships.php', From 0989c81aba72f714dec516033e07985c85cc7237 Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Thu, 12 Dec 2019 17:07:57 -0600 Subject: [PATCH 14/63] Set Eventbrite block to beta, for testing --- extensions/index.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/extensions/index.json b/extensions/index.json index 095006b6e042c..a3f0fdac617f8 100644 --- a/extensions/index.json +++ b/extensions/index.json @@ -3,7 +3,6 @@ "business-hours", "contact-form", "contact-info", - "eventbrite", "gif", "likes", "mailchimp", @@ -24,5 +23,10 @@ "videopress", "wordads" ], - "beta": [ "seo", "opentable", "calendly" ] + "beta": [ + "calendly", + "eventbrite", + "opentable", + "seo" + ] } From 9404d0741f36c9b9bee8d1572ccae42d8c0c7553 Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Thu, 12 Dec 2019 17:43:59 -0600 Subject: [PATCH 15/63] Replace event list with placeholder in Eventbrite block --- extensions/blocks/eventbrite/edit.js | 104 ++++++++------------------- 1 file changed, 30 insertions(+), 74 deletions(-) diff --git a/extensions/blocks/eventbrite/edit.js b/extensions/blocks/eventbrite/edit.js index 9363897b5fb66..03eb50120ef49 100644 --- a/extensions/blocks/eventbrite/edit.js +++ b/extensions/blocks/eventbrite/edit.js @@ -4,7 +4,16 @@ import { __ } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; import apiFetch from '@wordpress/api-fetch'; -import { PanelBody, RadioControl, SelectControl, Spinner, Button } from '@wordpress/components'; +import { + Button, + ExternalLink, + PanelBody, + Placeholder, + RadioControl, + SelectControl, + Spinner, + TextControl, +} from '@wordpress/components'; import { InspectorControls } from '@wordpress/editor'; /** @@ -15,41 +24,7 @@ import requestExternalAccess from 'lib/sharing'; const defaultEvent = { label: __( 'Select event', 'jetpack' ), value: '' }; class EventbriteEdit extends Component { - state = { - fetchingEvents: true, - connectUrl: '', - events: [], - }; - - componentDidMount() { - this.getEvents(); - } - - getEvents = async () => { - const response = await apiFetch( { - path: '/jetpack/v4/integrations/eventbrite', - } ); - - // If the API returned a connection URL, save it and retry a little later. - if ( response.connect_url ) { - this.setState( { - connectUrl: response.connect_url, - } ); - return; - } - - // Save the events returned. - const events = response.events.map( event => ( { - label: event.post_title, - value: event.ID, - } ) ); - - this.setState( { - connectUrl: '', - events, - fetchingEvents: false, - } ); - }; + state = {}; setEvent = eventId => { this.props.setAttributes( { eventId: eventId } ); @@ -57,40 +32,10 @@ class EventbriteEdit extends Component { render() { const { eventId, useModal } = this.props.attributes; - const { events, fetchingEvents, connectUrl } = this.state; - - if ( connectUrl ) { - return ( -
- -
- ); - } - - if ( fetchingEvents ) { - return ( -
- -

{ __( 'Loading events…', 'jetpack' ) }

-
- ); - } return (
- + { /* this.props.setAttributes( { useModal: 'modal' === option } ) } /> - + */ } - + +
    +
  1. + { __( "Location the embed code for the event you'd like to share.", 'jetpack' ) } +
    + +
  2. +
  3. + { __( 'Paste the Embed code you copied from Eventbrite below.', 'jetpack' ) } +
    + +
  4. + { __( 'Learn more about embeds', 'jetpack' ) } +
+
); } From 5a5ba989e398fbf948276a9231ab6c0f6e071dce Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Fri, 13 Dec 2019 21:36:18 -0600 Subject: [PATCH 16/63] Adds embed parsing and a non-working embed preview --- extensions/blocks/eventbrite/edit.js | 183 ++++++++++++++++++++------- 1 file changed, 138 insertions(+), 45 deletions(-) diff --git a/extensions/blocks/eventbrite/edit.js b/extensions/blocks/eventbrite/edit.js index 03eb50120ef49..db306c386fe1c 100644 --- a/extensions/blocks/eventbrite/edit.js +++ b/extensions/blocks/eventbrite/edit.js @@ -1,75 +1,168 @@ /** * External dependencies */ -import { __ } from '@wordpress/i18n'; +import { __, _x } from '@wordpress/i18n'; import { Component } from '@wordpress/element'; -import apiFetch from '@wordpress/api-fetch'; +import { BlockControls } from '@wordpress/block-editor'; import { Button, ExternalLink, + IconButton, PanelBody, Placeholder, RadioControl, - SelectControl, - Spinner, TextControl, + Toolbar, } from '@wordpress/components'; import { InspectorControls } from '@wordpress/editor'; +import EmbedPreview from '../embed-block/embed-preview'; /** * Internal dependences */ -import requestExternalAccess from 'lib/sharing'; - -const defaultEvent = { label: __( 'Select event', 'jetpack' ), value: '' }; class EventbriteEdit extends Component { - state = {}; + state = { + embedCode: '', + editingEmbed: false, + }; + + getEventAttributes = () => { + const { embedCode } = this.state; + + const eventIdMatch = embedCode.match( /eventId.*?(\d+)/ ); + const useModalMatch = embedCode.match( /modal.*?(true|TRUE)/ ); + + return { + eventId: eventIdMatch ? eventIdMatch[ 1 ] : null, + useModal: !! useModalMatch, + }; + }; + + setEvent = event => { + if ( event ) { + event.preventDefault(); + } + + const eventAttributes = this.getEventAttributes(); - setEvent = eventId => { - this.props.setAttributes( { eventId: eventId } ); + this.props.setAttributes( eventAttributes ); + this.setState( { editingEmbed: false } ); + }; + + setEmbedCode = embedCode => { + this.setState( { embedCode } ); }; render() { + const { className, isSelected } = this.props; const { eventId, useModal } = this.props.attributes; + const { editingEmbed } = this.state; + const containerId = `eventbrite-widget-container-${ eventId }`; + const modalId = `eventbrite-widget-modal-trigger-${ eventId }`; + const html = + ` + ` + useModal + ? `` + : `
`; + const preview = { + html, + scripts: [ 'https://www.eventbrite.com/static/widgets/eb_widgets.js' ], + }; + + const sidebarControls = ( + + + this.props.setAttributes( { useModal: 'modal' === option } ) } + /> + + + ); + + if ( ! eventId || editingEmbed ) { + return ( +
+ { sidebarControls } + + +
    +
  1. + { __( "Location the embed code for the event you'd like to share.", 'jetpack' ) } +
    + +
  2. +
  3. + { __( 'Paste the Embed code you copied from Eventbrite below.', 'jetpack' ) } +
    +
    + + + +
  4. + + { __( 'Learn more about embeds', 'jetpack' ) } + +
+
+
+ ); + } return ( -
- { /* - - this.props.setAttributes( { useModal: 'modal' === option } ) } +
+ { sidebarControls } + + + + this.setState( { editingEmbed: true } ) } /> - - */ } - - -
    -
  1. - { __( "Location the embed code for the event you'd like to share.", 'jetpack' ) } -
    - -
  2. -
  3. - { __( 'Paste the Embed code you copied from Eventbrite below.', 'jetpack' ) } -
    - -
  4. - { __( 'Learn more about embeds', 'jetpack' ) } -
-
+
+
+ +
); } From e8c181cd70b09e7c0f3307872085ae1c0fae083b Mon Sep 17 00:00:00 2001 From: Gary Date: Mon, 16 Dec 2019 13:54:40 +1100 Subject: [PATCH 17/63] Use @wordpress/embed-block to generate the block. --- extensions/blocks/eventbrite/edit.js | 171 -------------------- extensions/blocks/eventbrite/eventbrite.php | 101 ++++++++++-- extensions/blocks/eventbrite/index.js | 18 +-- 3 files changed, 94 insertions(+), 196 deletions(-) delete mode 100644 extensions/blocks/eventbrite/edit.js diff --git a/extensions/blocks/eventbrite/edit.js b/extensions/blocks/eventbrite/edit.js deleted file mode 100644 index db306c386fe1c..0000000000000 --- a/extensions/blocks/eventbrite/edit.js +++ /dev/null @@ -1,171 +0,0 @@ -/** - * External dependencies - */ -import { __, _x } from '@wordpress/i18n'; -import { Component } from '@wordpress/element'; -import { BlockControls } from '@wordpress/block-editor'; -import { - Button, - ExternalLink, - IconButton, - PanelBody, - Placeholder, - RadioControl, - TextControl, - Toolbar, -} from '@wordpress/components'; -import { InspectorControls } from '@wordpress/editor'; -import EmbedPreview from '../embed-block/embed-preview'; - -/** - * Internal dependences - */ - -class EventbriteEdit extends Component { - state = { - embedCode: '', - editingEmbed: false, - }; - - getEventAttributes = () => { - const { embedCode } = this.state; - - const eventIdMatch = embedCode.match( /eventId.*?(\d+)/ ); - const useModalMatch = embedCode.match( /modal.*?(true|TRUE)/ ); - - return { - eventId: eventIdMatch ? eventIdMatch[ 1 ] : null, - useModal: !! useModalMatch, - }; - }; - - setEvent = event => { - if ( event ) { - event.preventDefault(); - } - - const eventAttributes = this.getEventAttributes(); - - this.props.setAttributes( eventAttributes ); - this.setState( { editingEmbed: false } ); - }; - - setEmbedCode = embedCode => { - this.setState( { embedCode } ); - }; - - render() { - const { className, isSelected } = this.props; - const { eventId, useModal } = this.props.attributes; - const { editingEmbed } = this.state; - const containerId = `eventbrite-widget-container-${ eventId }`; - const modalId = `eventbrite-widget-modal-trigger-${ eventId }`; - const html = - ` - ` + useModal - ? `` - : `
`; - const preview = { - html, - scripts: [ 'https://www.eventbrite.com/static/widgets/eb_widgets.js' ], - }; - - const sidebarControls = ( - - - this.props.setAttributes( { useModal: 'modal' === option } ) } - /> - - - ); - - if ( ! eventId || editingEmbed ) { - return ( -
- { sidebarControls } - - -
    -
  1. - { __( "Location the embed code for the event you'd like to share.", 'jetpack' ) } -
    - -
  2. -
  3. - { __( 'Paste the Embed code you copied from Eventbrite below.', 'jetpack' ) } -
    -
    - - - -
  4. - - { __( 'Learn more about embeds', 'jetpack' ) } - -
-
-
- ); - } - - return ( -
- { sidebarControls } - - - - this.setState( { editingEmbed: true } ) } - /> - - - - -
- ); - } -} - -export default EventbriteEdit; diff --git a/extensions/blocks/eventbrite/eventbrite.php b/extensions/blocks/eventbrite/eventbrite.php index e84302d238b81..d4eea722c484e 100644 --- a/extensions/blocks/eventbrite/eventbrite.php +++ b/extensions/blocks/eventbrite/eventbrite.php @@ -7,22 +7,20 @@ * @package Jetpack */ -if ( ( defined( 'IS_WPCOM' ) && IS_WPCOM ) || Jetpack::is_active() ) { - jetpack_register_block( - 'jetpack/eventbrite', - array( - 'attributes' => array( - 'eventId' => array( - 'type' => 'string', - ), - 'useModal' => array( - 'type' => 'boolean', - ), +jetpack_register_block( + 'jetpack/eventbrite', + array( + 'attributes' => array( + 'eventId' => array( + 'type' => 'string', ), - 'render_callback' => 'jetpack_eventbrite_block_load_assets', - ) - ); -} + 'useModal' => array( + 'type' => 'boolean', + ), + ), + 'render_callback' => 'jetpack_eventbrite_block_load_assets', + ) +); /** * Eventbrite block registration/dependency delclaration. @@ -74,3 +72,76 @@ function jetpack_eventbrite_block_load_assets( $attr ) { EOT; } + + +/** + * Return fake embed data. + * + * @param array $matches Foo. + * @return string + */ +function jetpack_eventbrite_embed_handler( $matches ) { + if ( is_ssl() ) { + // phpcs:disable WordPress.WP.EnqueuedResources + return << + +
+SSLEMBED; + // phpcs:enable + } + + return <<\d+)\/?$/', 'jetpack_eventbrite_embed_handler' ); + +/** + * Handle a failing oEmbed proxy request to try embedding as a shortcode. + * + * @see https://core.trac.wordpress.org/ticket/45447 + * + * @param WP_HTTP_Response|WP_Error $response The REST Request response. + * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server). + * @param WP_REST_Request $request Request used to generate the response. + * @return WP_HTTP_Response|object|WP_Error The REST Request response. + */ +function jetpack_filter_oembed_result( $response, $handler, $request ) { + if ( ! is_wp_error( $response ) || 'oembed_invalid_url' !== $response->get_error_code() || + '/oembed/1.0/proxy' !== $request->get_route() ) { + return $response; + } + + // Try using a classic embed instead. + global $wp_embed; + // phpcs:ignore WordPress.Security.NonceVerification + $html = $wp_embed->shortcode( array(), $_GET['url'] ); + if ( ! $html ) { + return $response; + } + + global $wp_scripts; + + // Check if any scripts were enqueued by the shortcode, and include them in + // the response. + $enqueued_scripts = array(); + foreach ( $wp_scripts->queue as $script ) { + $enqueued_scripts[] = $wp_scripts->registered[ $script ]->src; + } + + return array( + 'provider_name' => __( 'Embed Handler', 'jetpack' ), + 'html' => $html, + 'scripts' => $enqueued_scripts, + ); +} +if ( ! function_exists( 'gutenberg_filter_oembed_result' ) ) { + add_filter( 'rest_request_after_callbacks', 'jetpack_filter_oembed_result', 10, 3 ); +} diff --git a/extensions/blocks/eventbrite/index.js b/extensions/blocks/eventbrite/index.js index 7fdc769efbff6..8f33e60126a11 100644 --- a/extensions/blocks/eventbrite/index.js +++ b/extensions/blocks/eventbrite/index.js @@ -2,25 +2,21 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; - -/** - * Internal dependencies - */ -import edit from './edit'; +import { embedContentIcon, getEmbedBlockSettings } from '@wordpress/embed-block'; export const name = 'eventbrite'; // Should this be 'Eventbrite Tickets', since we may add other embeds in the future? export const title = __( 'Eventbrite', 'jetpack' ); -export const icon = null; +export const icon = embedContentIcon; -export const settings = { +const definition = { title, description: __( 'Embed Eventbrite event details and ticket checkout.', 'jetpack' ), - // icon, + icon, category: 'jetpack', @@ -37,7 +33,9 @@ export const settings = { }, }, - edit, + responsive: false, - save: () => null, + patterns: [ /^https?:\/\/(.+?\.)?eventbrite\.com(\.[a-z]{2,4})*\/.+/i ], }; + +export const settings = getEmbedBlockSettings( definition ); From fd0681d0c88d09718eb02f83a42409830c399795 Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Mon, 16 Dec 2019 17:42:31 -0600 Subject: [PATCH 18/63] Eventbrite embed using prior art from Pinterest block --- ...-rest-api-v2-endpoint-resolve-redirect.php | 4 +- extensions/blocks/eventbrite/edit.js | 232 ++++++++++++++++++ extensions/blocks/eventbrite/index.js | 47 +++- extensions/blocks/eventbrite/utils.js | 17 ++ 4 files changed, 289 insertions(+), 11 deletions(-) create mode 100644 extensions/blocks/eventbrite/edit.js create mode 100644 extensions/blocks/eventbrite/utils.js diff --git a/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-resolve-redirect.php b/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-resolve-redirect.php index 442a2efa158c6..2a82af855dcdf 100644 --- a/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-resolve-redirect.php +++ b/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-resolve-redirect.php @@ -63,12 +63,12 @@ public function follow_redirect( $request ) { $history = $response['http_response']->get_response_object()->history; if ( ! $history ) { - return response_ensure_response( $request['url'] ); + return rest_ensure_response( $request['url'] ); } $location = $history[0]->headers->getValues( 'location' ); if ( ! $location ) { - return response_ensure_response( $request['url'] ); + return rest_ensure_response( $request['url'] ); } return rest_ensure_response( $location[0] ); diff --git a/extensions/blocks/eventbrite/edit.js b/extensions/blocks/eventbrite/edit.js new file mode 100644 index 0000000000000..f09c511de23ff --- /dev/null +++ b/extensions/blocks/eventbrite/edit.js @@ -0,0 +1,232 @@ +/** + * External dependencies + */ +import { invoke } from 'lodash'; +import { __, _x } from '@wordpress/i18n'; +import { Component } from '@wordpress/element'; +import { + Button, + IconButton, + PanelBody, + Placeholder, + RadioControl, + SandBox, + Spinner, + Toolbar, +} from '@wordpress/components'; +import { BlockControls, BlockIcon } from '@wordpress/block-editor'; +import { InspectorControls } from '@wordpress/editor'; +import apiFetch from '@wordpress/api-fetch'; + +/** + * Internal dependences + */ +import { fallback } from './utils'; +// import { icon } from '.'; + +const EVENTBRITE_URL_REGEX = /^https?:\/\/(.+?\.)?eventbrite\.com(\.[a-z]{2,4})*\/.+/i; + +class EventbriteEdit extends Component { + state = { + editedUrl: this.props.attributes.url || '', + editingUrl: false, + // If this is a customized URL, we're going to need to find where it redirects to. + resolvingRedirect: EVENTBRITE_URL_REGEX.test( this.props.attributes.url ), + // The interactive-related magic comes from Core's EmbedPreview component, + // which currently isn't exported in a way we can use. + interactive: false, + }; + + componentDidMount() { + const { resolvingRedirect } = this.state; + + // Check if we need to resolve an Eventbrite URL immediately. + if ( resolvingRedirect ) { + this.resolveRedirect(); + } + } + + componentDidUpdate( prevProps, prevState ) { + // Check if an Eventbrite URL has been entered, so we need to resolve it. + if ( ! prevState.resolvingRedirect && this.state.resolvingRedirect ) { + this.resolveRedirect(); + } + } + + componentWillUnmount() { + invoke( this.fetchRequest, [ 'abort' ] ); + } + + resolveRedirect = () => { + const { url } = this.props.attributes; + + this.fetchRequest = apiFetch( { + path: `/wpcom/v2/resolve-redirect/${ url }`, + } ); + + this.fetchRequest.then( + resolvedUrl => { + // resolve + this.fetchRequest = null; + this.props.setAttributes( { url: resolvedUrl } ); + this.setState( { + resolvingRedirect: false, + editedUrl: resolvedUrl, + } ); + }, + xhr => { + // reject + if ( xhr.statusText === 'abort' ) { + return; + } + this.fetchRequest = null; + this.setState( { + resolvingRedirect: false, + editingUrl: true, + } ); + } + ); + }; + + static getDerivedStateFromProps( nextProps, state ) { + if ( ! nextProps.isSelected && state.interactive ) { + // We only want to change this when the block is not selected, because changing it when + // the block becomes selected makes the overlap disappear too early. Hiding the overlay + // happens on mouseup when the overlay is clicked. + return { interactive: false }; + } + + return null; + } + + hideOverlay = () => { + // This is called onMouseUp on the overlay. We can't respond to the `isSelected` prop + // changing, because that happens on mouse down, and the overlay immediately disappears, + // and the mouse event can end up in the preview content. We can't use onClick on + // the overlay to hide it either, because then the editor misses the mouseup event, and + // thinks we're multi-selecting blocks. + this.setState( { interactive: true } ); + }; + + setUrl = event => { + if ( event ) { + event.preventDefault(); + } + + const { editedUrl: url } = this.state; + + this.props.setAttributes( { url } ); + this.setState( { editingUrl: false } ); + + if ( EVENTBRITE_URL_REGEX.test( url ) ) { + // Setting the `resolvingRedirect` state here, then waiting for `componentDidUpdate()` to + // be called before actually resolving it ensures that the `editedUrl` state has also been + // updated before resolveRedirect() is called. + this.setState( { resolvingRedirect: true } ); + } + }; + + /** + * Render a preview of the Eventbrite embed. + * + * @returns {object} The UI displayed when user edits this block. + */ + render() { + const { attributes, cannotEmbed, className, preview } = this.props; + const { url, useModal } = attributes; + const { editedUrl, interactive, editingUrl, resolvingRedirect } = this.state; + + + + this.props.setAttributes( { useModal: 'modal' === option } ) } + /> + + ; + + if ( resolvingRedirect ) { + return ( +
+ +

{ __( 'Embedding…' ) }

+
+ ); + } + + const controls = ( + + + this.setState( { editingUrl: true } ) } + /> + + + ); + + if ( editingUrl || ! url || cannotEmbed ) { + return ( +
+ { controls } + }> +
+ this.setState( { editedUrl: event.target.value } ) } + /> + + { cannotEmbed && ( +

+ { __( 'Sorry, this content could not be embedded.', 'jetpack' ) } +
+ +

+ ) } +
+
+
+ ); + } + + // Disabled because the overlay div doesn't actually have a role or functionality + // as far as the user is concerned. We're just catching the first click so that + // the block can be selected without interacting with the embed preview that the overlay covers. + /* eslint-disable jsx-a11y/no-static-element-interactions */ + return ( +
+ { controls } +
+ + { ! interactive && ( +
+ ) } +
+
+ ); + } +} + +export default EventbriteEdit; diff --git a/extensions/blocks/eventbrite/index.js b/extensions/blocks/eventbrite/index.js index 8f33e60126a11..a75f028abaa1c 100644 --- a/extensions/blocks/eventbrite/index.js +++ b/extensions/blocks/eventbrite/index.js @@ -2,21 +2,28 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; -import { embedContentIcon, getEmbedBlockSettings } from '@wordpress/embed-block'; +import { createBlock } from '@wordpress/blocks'; + +/** + * Internal dependencies + */ +import edit from './edit'; + +export const URL_REGEX = /^\s*https?:\/\/(?:www\.)?(?:[a-z]{2}\.)?(?:eventbrite\.[a-z.]+)\/([^/]+)(\/[^/]+)?/i; export const name = 'eventbrite'; // Should this be 'Eventbrite Tickets', since we may add other embeds in the future? export const title = __( 'Eventbrite', 'jetpack' ); -export const icon = embedContentIcon; +export const icon = ''; -const definition = { +export const settings = { title, description: __( 'Embed Eventbrite event details and ticket checkout.', 'jetpack' ), - icon, + // icon, category: 'jetpack', @@ -25,7 +32,7 @@ const definition = { }, attributes: { - eventId: { + url: { type: 'string', }, useModal: { @@ -33,9 +40,31 @@ const definition = { }, }, - responsive: false, + edit, - patterns: [ /^https?:\/\/(.+?\.)?eventbrite\.com(\.[a-z]{2,4})*\/.+/i ], -}; + save: () => null, + + transforms: { + from: [ + { + type: 'raw', + isMatch: node => node.nodeName === 'P' && URL_REGEX.test( node.textContent ), + transform: node => { + return createBlock( 'jetpack/eventbrite', { + url: node.textContent.trim(), + } ); + }, + }, + ], + }, + + example: { + attributes: { + url: 'https://www.eventbrite.com/e/test-event-tickets-123456789', + }, + }, + + // responsive: false, -export const settings = getEmbedBlockSettings( definition ); + // patterns: [ /^https?:\/\/(.+?\.)?eventbrite\.com(\.[a-z]{2,4})*\/.+/i ], +}; diff --git a/extensions/blocks/eventbrite/utils.js b/extensions/blocks/eventbrite/utils.js new file mode 100644 index 0000000000000..ed0df40242019 --- /dev/null +++ b/extensions/blocks/eventbrite/utils.js @@ -0,0 +1,17 @@ +/** + * External dependencies + */ +import { renderToString } from '@wordpress/element'; +import { createBlock } from '@wordpress/blocks'; + +/** + * Fallback behaviour for unembeddable URLs. + * Creates a paragraph block containing a link to the URL, and calls `onReplace`. + * + * @param {string} url The URL that could not be embedded. + * @param {Function} onReplace Function to call with the created fallback block. + */ +export function fallback( url, onReplace ) { + const link = { url }; + onReplace( createBlock( 'core/paragraph', { content: renderToString( link ) } ) ); +} From 3d68c8dcd925dcee4add46e17275ce313acabdff Mon Sep 17 00:00:00 2001 From: Gary Pendergast Date: Tue, 17 Dec 2019 06:57:24 +0000 Subject: [PATCH 19/63] Add the Eventbrite icon --- extensions/blocks/eventbrite/index.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/extensions/blocks/eventbrite/index.js b/extensions/blocks/eventbrite/index.js index a75f028abaa1c..35a5e598bc036 100644 --- a/extensions/blocks/eventbrite/index.js +++ b/extensions/blocks/eventbrite/index.js @@ -2,6 +2,7 @@ * External dependencies */ import { __ } from '@wordpress/i18n'; +import { G, Path, Rect, SVG } from '@wordpress/components'; import { createBlock } from '@wordpress/blocks'; /** @@ -16,14 +17,21 @@ export const name = 'eventbrite'; // Should this be 'Eventbrite Tickets', since we may add other embeds in the future? export const title = __( 'Eventbrite', 'jetpack' ); -export const icon = ''; +export const icon = ( + + + + + + +); export const settings = { title, description: __( 'Embed Eventbrite event details and ticket checkout.', 'jetpack' ), - // icon, + icon, category: 'jetpack', From 10a08e26132fa3d0e6955e40bdc410250bbd7ee7 Mon Sep 17 00:00:00 2001 From: Gary Pendergast Date: Tue, 17 Dec 2019 06:58:03 +0000 Subject: [PATCH 20/63] Rename block to Eventbrite Tickets --- extensions/blocks/eventbrite/index.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/extensions/blocks/eventbrite/index.js b/extensions/blocks/eventbrite/index.js index 35a5e598bc036..4d597fa043d10 100644 --- a/extensions/blocks/eventbrite/index.js +++ b/extensions/blocks/eventbrite/index.js @@ -14,8 +14,7 @@ export const URL_REGEX = /^\s*https?:\/\/(?:www\.)?(?:[a-z]{2}\.)?(?:eventbrite\ export const name = 'eventbrite'; -// Should this be 'Eventbrite Tickets', since we may add other embeds in the future? -export const title = __( 'Eventbrite', 'jetpack' ); +export const title = __( 'Eventbrite Tickets', 'jetpack' ); export const icon = ( From 35ace425fe4c18ae4027a23d008d87ef09d00c62 Mon Sep 17 00:00:00 2001 From: Gary Pendergast Date: Tue, 17 Dec 2019 06:59:42 +0000 Subject: [PATCH 21/63] Remove some old code --- extensions/blocks/eventbrite/index.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/extensions/blocks/eventbrite/index.js b/extensions/blocks/eventbrite/index.js index 4d597fa043d10..27fcd1d342bcb 100644 --- a/extensions/blocks/eventbrite/index.js +++ b/extensions/blocks/eventbrite/index.js @@ -70,8 +70,4 @@ export const settings = { url: 'https://www.eventbrite.com/e/test-event-tickets-123456789', }, }, - - // responsive: false, - - // patterns: [ /^https?:\/\/(.+?\.)?eventbrite\.com(\.[a-z]{2,4})*\/.+/i ], }; From 7bb710edc2f7edb3eb32a0d64b079ec27e3aa814 Mon Sep 17 00:00:00 2001 From: Gary Pendergast Date: Tue, 17 Dec 2019 07:46:19 +0000 Subject: [PATCH 22/63] Add block previews when editing --- extensions/blocks/eventbrite/edit.js | 195 ++++++++------------ extensions/blocks/eventbrite/eventbrite.php | 97 ++-------- 2 files changed, 96 insertions(+), 196 deletions(-) diff --git a/extensions/blocks/eventbrite/edit.js b/extensions/blocks/eventbrite/edit.js index f09c511de23ff..efa740e654cb9 100644 --- a/extensions/blocks/eventbrite/edit.js +++ b/extensions/blocks/eventbrite/edit.js @@ -1,93 +1,35 @@ /** * External dependencies */ -import { invoke } from 'lodash'; import { __, _x } from '@wordpress/i18n'; -import { Component } from '@wordpress/element'; +import { Component, Fragment } from '@wordpress/element'; import { + Placeholder, + SandBox, Button, IconButton, + Toolbar, PanelBody, - Placeholder, RadioControl, - SandBox, - Spinner, - Toolbar, } from '@wordpress/components'; import { BlockControls, BlockIcon } from '@wordpress/block-editor'; import { InspectorControls } from '@wordpress/editor'; -import apiFetch from '@wordpress/api-fetch'; /** - * Internal dependences + * Internal dependencies */ import { fallback } from './utils'; -// import { icon } from '.'; - -const EVENTBRITE_URL_REGEX = /^https?:\/\/(.+?\.)?eventbrite\.com(\.[a-z]{2,4})*\/.+/i; +import { icon } from '.'; class EventbriteEdit extends Component { state = { editedUrl: this.props.attributes.url || '', editingUrl: false, - // If this is a customized URL, we're going to need to find where it redirects to. - resolvingRedirect: EVENTBRITE_URL_REGEX.test( this.props.attributes.url ), // The interactive-related magic comes from Core's EmbedPreview component, // which currently isn't exported in a way we can use. interactive: false, }; - componentDidMount() { - const { resolvingRedirect } = this.state; - - // Check if we need to resolve an Eventbrite URL immediately. - if ( resolvingRedirect ) { - this.resolveRedirect(); - } - } - - componentDidUpdate( prevProps, prevState ) { - // Check if an Eventbrite URL has been entered, so we need to resolve it. - if ( ! prevState.resolvingRedirect && this.state.resolvingRedirect ) { - this.resolveRedirect(); - } - } - - componentWillUnmount() { - invoke( this.fetchRequest, [ 'abort' ] ); - } - - resolveRedirect = () => { - const { url } = this.props.attributes; - - this.fetchRequest = apiFetch( { - path: `/wpcom/v2/resolve-redirect/${ url }`, - } ); - - this.fetchRequest.then( - resolvedUrl => { - // resolve - this.fetchRequest = null; - this.props.setAttributes( { url: resolvedUrl } ); - this.setState( { - resolvingRedirect: false, - editedUrl: resolvedUrl, - } ); - }, - xhr => { - // reject - if ( xhr.statusText === 'abort' ) { - return; - } - this.fetchRequest = null; - this.setState( { - resolvingRedirect: false, - editingUrl: true, - } ); - } - ); - }; - static getDerivedStateFromProps( nextProps, state ) { if ( ! nextProps.isSelected && state.interactive ) { // We only want to change this when the block is not selected, because changing it when @@ -117,13 +59,6 @@ class EventbriteEdit extends Component { this.props.setAttributes( { url } ); this.setState( { editingUrl: false } ); - - if ( EVENTBRITE_URL_REGEX.test( url ) ) { - // Setting the `resolvingRedirect` state here, then waiting for `componentDidUpdate()` to - // be called before actually resolving it ensures that the `editedUrl` state has also been - // updated before resolveRedirect() is called. - this.setState( { resolvingRedirect: true } ); - } }; /** @@ -132,62 +67,96 @@ class EventbriteEdit extends Component { * @returns {object} The UI displayed when user edits this block. */ render() { - const { attributes, cannotEmbed, className, preview } = this.props; + const { attributes, className, setAttributes } = this.props; const { url, useModal } = attributes; - const { editedUrl, interactive, editingUrl, resolvingRedirect } = this.state; - - - - this.props.setAttributes( { useModal: 'modal' === option } ) } - /> - - ; - - if ( resolvingRedirect ) { - return ( -
- -

{ __( 'Embedding…' ) }

-
- ); + const { editedUrl, interactive, editingUrl } = this.state; + + const eventId = url.substring( url.search( /\d+$/g ) ); + + let html = ` + + + `; + if ( useModal ) { + html += ` + + + `; + } else { + html += ` + +
+ `; } + const cannotEmbed = ! url; + const controls = ( - - - this.setState( { editingUrl: true } ) } - /> - - + + + + this.setState( { editingUrl: true } ) } + /> + + + + + setAttributes( { useModal: 'modal' === option } ) } + /> + + + ); - if ( editingUrl || ! url || cannotEmbed ) { + if ( editingUrl || ! url ) { return (
{ controls } - }> + } + >
this.setState( { editedUrl: event.target.value } ) } /> + + EOT; } - - -/** - * Return fake embed data. - * - * @param array $matches Foo. - * @return string - */ -function jetpack_eventbrite_embed_handler( $matches ) { - if ( is_ssl() ) { - // phpcs:disable WordPress.WP.EnqueuedResources - return << - -
-SSLEMBED; - // phpcs:enable - } - - return <<\d+)\/?$/', 'jetpack_eventbrite_embed_handler' ); - -/** - * Handle a failing oEmbed proxy request to try embedding as a shortcode. - * - * @see https://core.trac.wordpress.org/ticket/45447 - * - * @param WP_HTTP_Response|WP_Error $response The REST Request response. - * @param WP_REST_Server $handler ResponseHandler instance (usually WP_REST_Server). - * @param WP_REST_Request $request Request used to generate the response. - * @return WP_HTTP_Response|object|WP_Error The REST Request response. - */ -function jetpack_filter_oembed_result( $response, $handler, $request ) { - if ( ! is_wp_error( $response ) || 'oembed_invalid_url' !== $response->get_error_code() || - '/oembed/1.0/proxy' !== $request->get_route() ) { - return $response; - } - - // Try using a classic embed instead. - global $wp_embed; - // phpcs:ignore WordPress.Security.NonceVerification - $html = $wp_embed->shortcode( array(), $_GET['url'] ); - if ( ! $html ) { - return $response; - } - - global $wp_scripts; - - // Check if any scripts were enqueued by the shortcode, and include them in - // the response. - $enqueued_scripts = array(); - foreach ( $wp_scripts->queue as $script ) { - $enqueued_scripts[] = $wp_scripts->registered[ $script ]->src; - } - - return array( - 'provider_name' => __( 'Embed Handler', 'jetpack' ), - 'html' => $html, - 'scripts' => $enqueued_scripts, - ); -} -if ( ! function_exists( 'gutenberg_filter_oembed_result' ) ) { - add_filter( 'rest_request_after_callbacks', 'jetpack_filter_oembed_result', 10, 3 ); -} From 899891d52c9c52e5ebe4f889e1f52678b84230e7 Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Tue, 17 Dec 2019 15:33:21 -0600 Subject: [PATCH 23/63] Refactor Eventbrite embed edit - Break up the component into multiple render functions - Re-add the functionality to resolve redirects for custom Eventbrite urls - Add working regexs for embedable and custom Eventbrite URLs --- extensions/blocks/eventbrite/edit.js | 275 ++++++++++++++------ extensions/blocks/eventbrite/eventbrite.php | 6 +- extensions/blocks/eventbrite/index.js | 2 +- 3 files changed, 196 insertions(+), 87 deletions(-) diff --git a/extensions/blocks/eventbrite/edit.js b/extensions/blocks/eventbrite/edit.js index efa740e654cb9..ded5d5be0c991 100644 --- a/extensions/blocks/eventbrite/edit.js +++ b/extensions/blocks/eventbrite/edit.js @@ -11,25 +11,84 @@ import { Toolbar, PanelBody, RadioControl, + Spinner, } from '@wordpress/components'; import { BlockControls, BlockIcon } from '@wordpress/block-editor'; import { InspectorControls } from '@wordpress/editor'; +import apiFetch from '@wordpress/api-fetch'; /** * Internal dependencies */ import { fallback } from './utils'; -import { icon } from '.'; +import { icon, URL_REGEX } from '.'; + +// Custom eventbrite urls use a subdomain of eventbrite.com. +const EVENTBRITE_CUSTOM_URL_REGEX = /.*(?:eventbrite\.[a-z.]+)\/?\s*$/i; class EventbriteEdit extends Component { state = { editedUrl: this.props.attributes.url || '', editingUrl: false, + // If this is a customized URL, we're going to need to find where it redirects to. + resolvingRedirect: EVENTBRITE_CUSTOM_URL_REGEX.test( this.props.attributes.url ), // The interactive-related magic comes from Core's EmbedPreview component, // which currently isn't exported in a way we can use. interactive: false, }; + componentDidMount() { + const { resolvingRedirect } = this.state; + + // Check if we need to resolve an Eventbrite URL immediately. + if ( resolvingRedirect ) { + this.resolveRedirect(); + } + } + + componentDidUpdate( prevProps, prevState ) { + // Check if an Eventbrite URL has been entered, so we need to resolve it. + if ( ! prevState.resolvingRedirect && this.state.resolvingRedirect ) { + this.resolveRedirect(); + } + } + + // TODO: figure out how to cancel request since apiFetch was updated to use Promises rather than XHR requests. + componentWillUnmount() { + // invoke( this.fetchRequest, [ 'abort' ] ); + } + + resolveRedirect = () => { + const { url } = this.props.attributes; + + this.fetchRequest = apiFetch( { + path: `/wpcom/v2/resolve-redirect/${ url }`, + } ); + + this.fetchRequest.then( + resolvedUrl => { + // resolve + this.fetchRequest = null; + this.props.setAttributes( { url: resolvedUrl } ); + this.setState( { + resolvingRedirect: false, + editedUrl: resolvedUrl, + } ); + }, + xhr => { + // reject + if ( xhr.statusText === 'abort' ) { + return; + } + this.fetchRequest = null; + this.setState( { + resolvingRedirect: false, + editingUrl: true, + } ); + } + ); + }; + static getDerivedStateFromProps( nextProps, state ) { if ( ! nextProps.isSelected && state.interactive ) { // We only want to change this when the block is not selected, because changing it when @@ -59,19 +118,115 @@ class EventbriteEdit extends Component { this.props.setAttributes( { url } ); this.setState( { editingUrl: false } ); + + if ( EVENTBRITE_CUSTOM_URL_REGEX.test( url ) ) { + // Setting the `resolvingRedirect` state here, then waiting for `componentDidUpdate()` to + // be called before actually resolving it ensures that the `editedUrl` state has also been + // updated before resolveRedirect() is called. + this.setState( { resolvingRedirect: true } ); + } }; - /** - * Render a preview of the Eventbrite embed. - * - * @returns {object} The UI displayed when user edits this block. - */ - render() { - const { attributes, className, setAttributes } = this.props; - const { url, useModal } = attributes; - const { editedUrl, interactive, editingUrl } = this.state; + cannotEmbed = () => { + const { url } = this.props.attributes; + + return url && ! URL_REGEX.test( url ); + }; + + renderLoading() { + return ( +
+ +

{ __( 'Embedding…' ) }

+
+ ); + } + + renderControls() { + const { setAttributes } = this.props; + const { useModal } = this.props.attributes; + return ( + + + + this.setState( { editingUrl: true } ) } + /> + + + + + + setAttributes( { useModal: 'modal' === option } ) } + /> + + + + ); + } + + renderEditEmbed() { + const { className } = this.props; + const { editedUrl } = this.state; + return ( +
+ { this.renderControls() } + + } + > + + this.setState( { editedUrl: event.target.value } ) } + /> + + { this.cannotEmbed() && ( +

+ { __( 'Sorry, this content could not be embedded.', 'jetpack' ) } +
+ +

+ ) } + +
+
+ ); + } + + renderPreview() { + const { className } = this.props; + const { url, useModal } = this.props.attributes; + const { interactive } = this.state; + + const eventId = url ? url.substring( url.search( /\d+$/g ) ) : null; - const eventId = url.substring( url.search( /\d+$/g ) ); + if ( ! eventId ) { + return; + } let html = ` @@ -83,6 +238,7 @@ class EventbriteEdit extends Component { } `; + if ( useModal ) { html += ` From 1fbb47d3f28f53f6da3f49500b2cd9c6f4ce7c09 Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Wed, 18 Dec 2019 18:45:35 -0600 Subject: [PATCH 28/63] Re-render preview when embed type is changed --- extensions/blocks/eventbrite/edit.js | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/extensions/blocks/eventbrite/edit.js b/extensions/blocks/eventbrite/edit.js index 757bab53ee7ee..7d93159eaacd4 100644 --- a/extensions/blocks/eventbrite/edit.js +++ b/extensions/blocks/eventbrite/edit.js @@ -215,6 +215,7 @@ class EventbriteEdit extends Component { return; } + let widgetId; let html = ` + +
`; - if ( useModal ) { - widgetId = `eventbrite-widget-modal-trigger-${ eventId }`; - html += ` - - - `; - } else { - widgetId = `eventbrite-widget-container-${ eventId }`; - html += ` - -
- `; - } - return (
- - - this.setState( { editingUrl: true } ) } - /> - - - - + { /* Use an overlay to prevent interactivity with the preview, since the modal does not resize correctly. */ }
@@ -269,7 +256,7 @@ class EventbriteEdit extends Component { */ render() { const { attributes } = this.props; - const { url } = attributes; + const { url, useModal } = attributes; const { editingUrl, resolvingUrl } = this.state; let component; @@ -279,12 +266,17 @@ class EventbriteEdit extends Component { } else if ( editingUrl || ! url || this.cannotEmbed() ) { component = this.renderEditEmbed(); } else { - component = this.renderPreview(); + component = ( + <> + { this.renderBlockControls() } + { useModal ? : this.renderInlinePreview() } + + ); } return ( <> - { this.renderControls() } + { this.renderInspectorControls() } { component } ); diff --git a/extensions/blocks/eventbrite/eventbrite.php b/extensions/blocks/eventbrite/eventbrite.php index d4fd7f35f25ae..f5c1d5e5f100c 100644 --- a/extensions/blocks/eventbrite/eventbrite.php +++ b/extensions/blocks/eventbrite/eventbrite.php @@ -25,11 +25,12 @@ /** * Eventbrite block registration/dependency delclaration. * - * @param array $attr Eventbrite block attributes. + * @param array $attr Eventbrite block attributes. + * @param string $content Rendered embed element (without scripts) from the block editor. * * @return string */ -function jetpack_eventbrite_block_load_assets( $attr ) { +function jetpack_eventbrite_block_load_assets( $attr, $content ) { if ( empty( $attr['url'] ) ) { return ''; } @@ -56,7 +57,7 @@ function jetpack_eventbrite_block_load_assets( $attr ) { ); return <<
+${content} @@ -76,7 +77,7 @@ function jetpack_eventbrite_block_load_assets( $attr ) { return << - - + ${content} + EOT; } diff --git a/extensions/blocks/eventbrite/index.js b/extensions/blocks/eventbrite/index.js index 6660b4bfe0ccf..b464fec9744c8 100644 --- a/extensions/blocks/eventbrite/index.js +++ b/extensions/blocks/eventbrite/index.js @@ -9,6 +9,7 @@ import { createBlock } from '@wordpress/blocks'; * Internal dependencies */ import edit from './edit'; +import save from './save'; export const URL_REGEX = /^\s*https?:\/\/(?:www\.)?(?:eventbrite\.[a-z.]+)\/e\/[^/]*?(\d+)\/?\s*$/i; @@ -47,11 +48,30 @@ export const settings = { useModal: { type: 'boolean', }, + // Modal button attributes. + text: { + type: 'string', + }, + backgroundColor: { + type: 'string', + }, + textColor: { + type: 'string', + }, + customBackgroundColor: { + type: 'string', + }, + customTextColor: { + type: 'string', + }, + borderRadius: { + type: 'number', + }, }, edit, - save: () => null, + save, transforms: { from: [ diff --git a/extensions/blocks/eventbrite/modal-button-preview.js b/extensions/blocks/eventbrite/modal-button-preview.js new file mode 100644 index 0000000000000..f40e88bbefa52 --- /dev/null +++ b/extensions/blocks/eventbrite/modal-button-preview.js @@ -0,0 +1,136 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; + +/** + * WordPress dependencies + */ +import { __ } from '@wordpress/i18n'; +import { useCallback } from '@wordpress/element'; +import { compose, withInstanceId } from '@wordpress/compose'; +import { PanelBody, RangeControl, withFallbackStyles } from '@wordpress/components'; +import { + ContrastChecker, + InspectorControls, + PanelColorSettings, + RichText, + withColors, +} from '@wordpress/block-editor'; + +const { getComputedStyle } = window; + +const applyFallbackStyles = withFallbackStyles( ( node, ownProps ) => { + const { textColor, backgroundColor } = ownProps; + const backgroundColorValue = backgroundColor && backgroundColor.color; + const textColorValue = textColor && textColor.color; + //avoid the use of querySelector if textColor color is known and verify if node is available. + const textNode = + ! textColorValue && node ? node.querySelector( '[contenteditable="true"]' ) : null; + return { + fallbackBackgroundColor: + backgroundColorValue || ! node ? undefined : getComputedStyle( node ).backgroundColor, + fallbackTextColor: + textColorValue || ! textNode ? undefined : getComputedStyle( textNode ).color, + }; +} ); + +const MIN_BORDER_RADIUS_VALUE = 0; +const MAX_BORDER_RADIUS_VALUE = 50; +const INITIAL_BORDER_RADIUS_POSITION = 5; + +function BorderPanel( { borderRadius = '', setAttributes } ) { + const setBorderRadius = useCallback( + newBorderRadius => { + setAttributes( { borderRadius: newBorderRadius } ); + }, + [ setAttributes ] + ); + return ( + + + + ); +} + +function ButtonEdit( { + attributes, + backgroundColor, + textColor, + setBackgroundColor, + setTextColor, + fallbackBackgroundColor, + fallbackTextColor, + setAttributes, + className, +} ) { + const { borderRadius, text, title } = attributes; + return ( +
+ setAttributes( { text: value } ) } + withoutInteractiveFormatting + className={ classnames( 'wp-block-button__link', { + 'has-background': backgroundColor.color, + [ backgroundColor.class ]: backgroundColor.class, + 'has-text-color': textColor.color, + [ textColor.class ]: textColor.class, + 'no-border-radius': borderRadius === 0, + } ) } + style={ { + backgroundColor: backgroundColor.color, + color: textColor.color, + borderRadius: borderRadius ? borderRadius + 'px' : undefined, + } } + /> + + { + setBackgroundColor( newColor ); + }, + label: __( 'Background Color' ), + }, + { + value: textColor.color, + onChange: setTextColor, + label: __( 'Text Color' ), + }, + ] } + > + + + + +
+ ); +} + +export default compose( [ + withInstanceId, + withColors( 'backgroundColor', { textColor: 'color' } ), + applyFallbackStyles, +] )( ButtonEdit ); diff --git a/extensions/blocks/eventbrite/save.js b/extensions/blocks/eventbrite/save.js new file mode 100644 index 0000000000000..9ef6fe85e3e99 --- /dev/null +++ b/extensions/blocks/eventbrite/save.js @@ -0,0 +1,63 @@ +/** + * External dependencies + */ +import classnames from 'classnames'; +import { RichText, getColorClassName } from '@wordpress/block-editor'; + +/** + * Internal dependencies + */ +import { eventIdFromUrl } from './utils'; + +function saveButton( eventId, attributes ) { + const { + backgroundColor, + borderRadius, + customBackgroundColor, + customTextColor, + text, + textColor, + } = attributes; + + const textClass = getColorClassName( 'color', textColor ); + const backgroundClass = getColorClassName( 'background-color', backgroundColor ); + + const buttonClasses = classnames( 'wp-block-button__link', { + 'has-text-color': textColor || customTextColor, + [ textClass ]: textClass, + 'has-background': backgroundColor || customBackgroundColor, + [ backgroundClass ]: backgroundClass, + 'no-border-radius': borderRadius === 0, + } ); + + const buttonStyle = { + backgroundColor: backgroundClass ? undefined : customBackgroundColor, + color: textClass ? undefined : customTextColor, + borderRadius: borderRadius ? borderRadius + 'px' : undefined, + }; + + return ( +
+ +
+ ); +} + +export default function save( { attributes } ) { + const { useModal, url } = attributes; + + const eventId = eventIdFromUrl( url ); + + if ( useModal ) { + return saveButton( eventId, attributes ); + } + + return
; +} diff --git a/extensions/blocks/eventbrite/utils.js b/extensions/blocks/eventbrite/utils.js index ed0df40242019..34f2ae3c2603d 100644 --- a/extensions/blocks/eventbrite/utils.js +++ b/extensions/blocks/eventbrite/utils.js @@ -15,3 +15,7 @@ export function fallback( url, onReplace ) { const link = { url }; onReplace( createBlock( 'core/paragraph', { content: renderToString( link ) } ) ); } + +export function eventIdFromUrl( url ) { + return url.substring( url.search( /\d+$/g ) ); +} From 055012f81234aa24bad135a6b4f202617bd4be94 Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Wed, 8 Jan 2020 14:52:54 -0600 Subject: [PATCH 34/63] Adds comment explaining header for reqeuest to resolve redirect --- .../class-wpcom-rest-api-v2-endpoint-resolve-redirect.php | 1 + 1 file changed, 1 insertion(+) diff --git a/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-resolve-redirect.php b/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-resolve-redirect.php index 12f576fc03e9d..e2bd3ed804027 100644 --- a/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-resolve-redirect.php +++ b/_inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-resolve-redirect.php @@ -56,6 +56,7 @@ public function register_routes() { * @return WP_REST_Response The REST API response. */ public function follow_redirect( $request ) { + // Add a User-Agent header since the request is sometimes blocked without it. $response = wp_safe_remote_get( $request['url'], array( From 58c7d3881be4732df7b90fab8814aebe13f48878 Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Wed, 8 Jan 2020 15:58:52 -0600 Subject: [PATCH 35/63] Adds comments to files adapted from @wordpress/block-library --- .../blocks/eventbrite/modal-button-preview.js | 165 ++++++++++-------- extensions/blocks/eventbrite/save.js | 10 ++ 2 files changed, 105 insertions(+), 70 deletions(-) diff --git a/extensions/blocks/eventbrite/modal-button-preview.js b/extensions/blocks/eventbrite/modal-button-preview.js index f40e88bbefa52..1f132fc15f0f4 100644 --- a/extensions/blocks/eventbrite/modal-button-preview.js +++ b/extensions/blocks/eventbrite/modal-button-preview.js @@ -1,3 +1,13 @@ +/** + * Adapted ButtonEdit component from @wordpress/block-library + * (Using Gutenberg code that shipped with WordPress 5.3) + * + * @see https://github.com/WordPress/gutenberg/blob/wp/5.2/packages/block-library/src/button/edit.js + * + * Removes url for button, since the button triggers a modal in this block + * rather than opening a link. + */ + /** * External dependencies */ @@ -7,15 +17,15 @@ import classnames from 'classnames'; * WordPress dependencies */ import { __ } from '@wordpress/i18n'; -import { useCallback } from '@wordpress/element'; -import { compose, withInstanceId } from '@wordpress/compose'; +import { Component, useCallback } from '@wordpress/element'; +import { compose } from '@wordpress/compose'; import { PanelBody, RangeControl, withFallbackStyles } from '@wordpress/components'; import { + RichText, ContrastChecker, InspectorControls, - PanelColorSettings, - RichText, withColors, + PanelColorSettings, } from '@wordpress/block-editor'; const { getComputedStyle } = window; @@ -61,76 +71,91 @@ function BorderPanel( { borderRadius = '', setAttributes } ) { ); } -function ButtonEdit( { - attributes, - backgroundColor, - textColor, - setBackgroundColor, - setTextColor, - fallbackBackgroundColor, - fallbackTextColor, - setAttributes, - className, -} ) { - const { borderRadius, text, title } = attributes; - return ( -
- setAttributes( { text: value } ) } - withoutInteractiveFormatting - className={ classnames( 'wp-block-button__link', { - 'has-background': backgroundColor.color, - [ backgroundColor.class ]: backgroundColor.class, - 'has-text-color': textColor.color, - [ textColor.class ]: textColor.class, - 'no-border-radius': borderRadius === 0, - } ) } - style={ { - backgroundColor: backgroundColor.color, - color: textColor.color, - borderRadius: borderRadius ? borderRadius + 'px' : undefined, - } } - /> - - { - setBackgroundColor( newColor ); +class ButtonEdit extends Component { + constructor() { + super( ...arguments ); + this.nodeRef = null; + this.bindRef = this.bindRef.bind( this ); + } + + bindRef( node ) { + if ( ! node ) { + return; + } + this.nodeRef = node; + } + + render() { + const { + attributes, + backgroundColor, + textColor, + setBackgroundColor, + setTextColor, + fallbackBackgroundColor, + fallbackTextColor, + setAttributes, + className, + } = this.props; + + const { borderRadius, placeholder, text, title } = attributes; + + return ( +
+ setAttributes( { text: value } ) } + withoutInteractiveFormatting + className={ classnames( 'wp-block-button__link', { + 'has-background': backgroundColor.color, + [ backgroundColor.class ]: backgroundColor.class, + 'has-text-color': textColor.color, + [ textColor.class ]: textColor.class, + 'no-border-radius': borderRadius === 0, + } ) } + style={ { + backgroundColor: backgroundColor.color, + color: textColor.color, + borderRadius: borderRadius ? borderRadius + 'px' : undefined, + } } + /> + + - - - - -
- ); + { + value: textColor.color, + onChange: setTextColor, + label: __( 'Text Color' ), + }, + ] } + > + +
+ +
+
+ ); + } } export default compose( [ - withInstanceId, withColors( 'backgroundColor', { textColor: 'color' } ), applyFallbackStyles, ] )( ButtonEdit ); diff --git a/extensions/blocks/eventbrite/save.js b/extensions/blocks/eventbrite/save.js index 9ef6fe85e3e99..4d5f27ad0a49e 100644 --- a/extensions/blocks/eventbrite/save.js +++ b/extensions/blocks/eventbrite/save.js @@ -9,6 +9,16 @@ import { RichText, getColorClassName } from '@wordpress/block-editor'; */ import { eventIdFromUrl } from './utils'; +/** + * Adapted button save function from @wordpress/block-library + * (Using Gutenberg code that shipped with WordPress 5.3) + * + * @see https://github.com/WordPress/gutenberg/blob/wp/5.3/packages/block-library/src/button/save.js + * + * Uses a "button" element rather than "a", since the button opens a modal rather than + * an external link. + */ + function saveButton( eventId, attributes ) { const { backgroundColor, From d0cb298285c9e256e38c99ada5ef42ccef9953ca Mon Sep 17 00:00:00 2001 From: Grant Kinney Date: Thu, 9 Jan 2020 16:12:48 -0600 Subject: [PATCH 36/63] Share block settings between php and js - Consolidate and update event url regexes - Consolidate generation of widget html id --- extensions/blocks/eventbrite/edit.js | 11 +++----- extensions/blocks/eventbrite/eventbrite.php | 28 +++++++++++++++++---- extensions/blocks/eventbrite/index.js | 12 +++++++-- extensions/blocks/eventbrite/save.js | 10 +++++--- extensions/blocks/eventbrite/utils.js | 8 +++++- 5 files changed, 51 insertions(+), 18 deletions(-) diff --git a/extensions/blocks/eventbrite/edit.js b/extensions/blocks/eventbrite/edit.js index cbd71c1e30952..08a6a414bf63b 100644 --- a/extensions/blocks/eventbrite/edit.js +++ b/extensions/blocks/eventbrite/edit.js @@ -20,19 +20,16 @@ import apiFetch from '@wordpress/api-fetch'; /** * Internal dependencies */ -import { fallback, eventIdFromUrl } from './utils'; -import { icon, URL_REGEX } from '.'; +import { createWidgetId, fallback, eventIdFromUrl } from './utils'; +import { CUSTOM_URL_REGEX, icon, URL_REGEX } from '.'; import ModalButtonPreview from './modal-button-preview'; -// Custom eventbrite urls use a subdomain of eventbrite.com. -const EVENTBRITE_CUSTOM_URL_REGEX = /.*(?:eventbrite\.[a-z.]+)\/?\s*$/i; - class EventbriteEdit extends Component { state = { editedUrl: this.props.attributes.url || '', editingUrl: false, // If this is a customized URL, we're going to need to find where it redirects to. - resolvingUrl: EVENTBRITE_CUSTOM_URL_REGEX.test( this.props.attributes.url ), + resolvingUrl: CUSTOM_URL_REGEX.test( this.props.attributes.url ), resolvedStatusCode: null, }; @@ -220,7 +217,7 @@ class EventbriteEdit extends Component { return; } - const widgetId = `eventbrite-widget-container-${ eventId }`; + const widgetId = createWidgetId( eventId ); const html = `