diff --git a/includes/class-newspack-newsletters.php b/includes/class-newspack-newsletters.php
index e2574b322..020f5ded8 100644
--- a/includes/class-newspack-newsletters.php
+++ b/includes/class-newspack-newsletters.php
@@ -160,6 +160,12 @@ public static function enqueue_block_editor_assets() {
return;
}
+ $mailchimp_api_key = self::mailchimp_api_key();
+ $mjml_api_key = get_option( 'newspack_newsletters_mjml_api_key', false );
+ $mjml_api_secret = get_option( 'newspack_newsletters_mjml_api_secret', false );
+
+ $has_keys = ! empty( $mailchimp_api_key ) && ! empty( $mjml_api_key ) && ! empty( $mjml_api_secret );
+
\wp_enqueue_script(
'newspack-newsletters',
plugins_url( '../dist/editor.js', __FILE__ ),
@@ -173,6 +179,7 @@ public static function enqueue_block_editor_assets() {
'newspack_newsletters_data',
[
'templates' => self::get_newsletter_templates(),
+ 'has_keys' => $has_keys,
]
);
@@ -275,6 +282,123 @@ public static function rest_api_init() {
],
]
);
+ \register_rest_route(
+ 'newspack-newsletters/v1/',
+ 'keys',
+ [
+ 'methods' => \WP_REST_Server::READABLE,
+ 'callback' => [ __CLASS__, 'api_get_keys' ],
+ 'permission_callback' => [ __CLASS__, 'api_permissions_check' ],
+ ]
+ );
+ \register_rest_route(
+ 'newspack-newsletters/v1/',
+ 'keys',
+ [
+ 'methods' => \WP_REST_Server::EDITABLE,
+ 'callback' => [ __CLASS__, 'api_set_keys' ],
+ 'permission_callback' => [ __CLASS__, 'api_permissions_check' ],
+ 'args' => [
+ 'mailchimp_api_key' => [
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ 'mjml_application_id' => [
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ 'mjml_api_secret' => [
+ 'sanitize_callback' => 'sanitize_text_field',
+ ],
+ ],
+ ]
+ );
+ }
+
+ /**
+ * Retrieve API keys.
+ */
+ public static function api_get_keys() {
+ $mailchimp_api_key = self::mailchimp_api_key();
+ $mjml_api_key = get_option( 'newspack_newsletters_mjml_api_key', false );
+ $mjml_api_secret = get_option( 'newspack_newsletters_mjml_api_secret', false );
+
+ $keys = [
+ 'mailchimp_api_key' => $mailchimp_api_key,
+ 'mjml_api_key' => $mjml_api_key,
+ 'mjml_api_secret' => $mjml_api_secret,
+ 'status' => ! empty( $mailchimp_api_key ) && ! empty( $mjml_api_key ) && ! empty( $mjml_api_secret ),
+ ];
+ return \rest_ensure_response( $keys );
+ }
+
+ /**
+ * Set API keys.
+ *
+ * @param WP_REST_Request $request API request object.
+ */
+ public static function api_set_keys( $request ) {
+ $mailchimp_api_key = $request['mailchimp_api_key'];
+ $mjml_api_key = $request['mjml_api_key'];
+ $mjml_api_secret = $request['mjml_api_secret'];
+ $wp_error = new WP_Error();
+
+ $errors = [];
+
+ if ( empty( $mailchimp_api_key ) ) {
+ $wp_error->add(
+ 'newspack_newsletters_invalid_keys_mailchimp',
+ __( 'Please input a Mailchimp API key.', 'newspack-newsletters' )
+ );
+ } else {
+ try {
+ $mc = new Mailchimp( $mailchimp_api_key );
+ $ping = $mc->get( 'ping' );
+ } catch ( Exception $e ) {
+ $ping = null;
+ }
+ if ( $ping ) {
+ update_option( 'newspack_newsletters_mailchimp_api_key', $mailchimp_api_key );
+ } else {
+ $wp_error->add(
+ 'newspack_newsletters_invalid_keys_mailchimp',
+ __( 'Please input a valid Mailchimp API key.', 'newspack-newsletters' )
+ );
+ }
+ }
+
+ if ( empty( $mjml_api_key ) || empty( $mjml_api_secret ) ) {
+ $wp_error->add(
+ 'newspack_newsletters_invalid_keys_mjml',
+ __( 'Please input MJML application ID and secret key.', 'newspack-newsletters' )
+ );
+ } else {
+ $credentials = "$mjml_api_key:$mjml_api_secret";
+ $url = 'https://api.mjml.io/v1/render';
+ $mjml_test = wp_remote_post(
+ $url,
+ [
+ 'body' => wp_json_encode(
+ [
+ 'mjml' => '
test
',
+ ]
+ ),
+ 'headers' => array(
+ 'Authorization' => 'Basic ' . base64_encode( $credentials ),
+ ),
+ 'timeout' => 10, // phpcs:ignore WordPressVIPMinimum.Performance.RemoteRequestTimeout.timeout_timeout
+ ]
+ );
+ if ( 200 === $mjml_test['response']['code'] ) {
+ update_option( 'newspack_newsletters_mjml_api_key', $mjml_api_key );
+ update_option( 'newspack_newsletters_mjml_api_secret', $mjml_api_secret );
+ } else {
+ $wp_error->add(
+ 'newspack_newsletters_invalid_keys_mjml',
+ __( 'Please input valid MJML application ID and secret key.', 'newspack-newsletters' )
+ );
+ }
+ }
+
+ return $wp_error->has_errors() ? $wp_error : self::api_get_keys();
}
/**
diff --git a/src/components/template-modal/index.js b/src/components/template-modal/index.js
index f080e4985..8fd461fa2 100644
--- a/src/components/template-modal/index.js
+++ b/src/components/template-modal/index.js
@@ -5,88 +5,35 @@
/**
* WordPress dependencies
*/
-import { parse } from '@wordpress/blocks';
-import { __, sprintf } from '@wordpress/i18n';
-import { Component } from '@wordpress/element';
-import { Button, Modal } from '@wordpress/components';
-import { ENTER, SPACE } from '@wordpress/keycodes';
-import { BlockPreview } from '@wordpress/block-editor';
+import { Modal } from '@wordpress/components';
+import { __ } from '@wordpress/i18n';
/**
* Internal dependencies
*/
+import TemplatePicker from './screens/template-picker';
+import APIKeys from './screens/api-keys';
import './style.scss';
-class TemplateModal extends Component {
- generateBlockPreview = () => {
- const { selectedTemplate, templates } = this.props;
- return templates && templates[ selectedTemplate ]
- ? parse( templates[ selectedTemplate ].content )
- : null;
- };
- render = () => {
- const { onInsertTemplate, onSelectTemplate, selectedTemplate, templates } = this.props;
- const blockPreview = this.generateBlockPreview();
- return (
-
-
-
-
- { ( templates || [] ).map( ( { title, content }, index ) => (
-
onSelectTemplate( index ) }
- onKeyDown={ event => {
- if ( ENTER === event.keyCode || SPACE === event.keyCode ) {
- event.preventDefault();
- onSelectTemplate( index );
- }
- } }
- role="button"
- tabIndex="0"
- aria-label={ title }
- >
-
-
-
-
{ title }
-
- ) ) }
-
-
-
-
- { blockPreview && blockPreview.length > 0 ? (
-
- ) : (
-
{ __( 'Select a layout to preview.', 'newspack-newsletters' ) }
- ) }
-
-
-
- { selectedTemplate !== null && (
-
- ) }
-
- );
- };
-}
-
-export default TemplateModal;
+export default ( { hasKeys, onInsertTemplate, onSetupStatus, templates } ) => {
+ return (
+
+ { hasKeys ? (
+
+ ) : (
+
+ ) }
+
+ );
+};
diff --git a/src/components/template-modal/screens/api-keys/index.js b/src/components/template-modal/screens/api-keys/index.js
new file mode 100644
index 000000000..fb5f2e2a6
--- /dev/null
+++ b/src/components/template-modal/screens/api-keys/index.js
@@ -0,0 +1,117 @@
+/**
+ * WordPress dependencies
+ */
+import apiFetch from '@wordpress/api-fetch';
+import { Button, ExternalLink, Spinner, TextControl } from '@wordpress/components';
+import { Fragment, useEffect, useState } from '@wordpress/element';
+import { __ } from '@wordpress/i18n';
+import { ENTER } from '@wordpress/keycodes';
+
+export default ( { onSetupStatus } ) => {
+ const [ keys, setKeys ] = useState( {} );
+ const [ inFlight, setInFlight ] = useState( false );
+ const [ errors, setErrors ] = useState( {} );
+ const commitSettings = () => {
+ setInFlight( true );
+ setErrors( {} );
+ apiFetch( {
+ path: '/newspack-newsletters/v1/keys',
+ method: 'POST',
+ data: keys,
+ } )
+ .then( results => {
+ setInFlight( false );
+ setKeys( results );
+ onSetupStatus( results.status );
+ } )
+ .catch( handleErrors );
+ };
+ const handleErrors = error => {
+ const allErrors = { [ error.code ]: error.message };
+ ( error.additional_errors || [] ).forEach(
+ additionalError => ( allErrors[ additionalError.code ] = additionalError.message )
+ );
+ setErrors( allErrors );
+ setInFlight( false );
+ };
+ useEffect(() => {
+ setInFlight( true );
+ apiFetch( { path: `/newspack-newsletters/v1/keys` } )
+ .then( results => {
+ setInFlight( false );
+ setKeys( results );
+ onSetupStatus( results.status );
+ } )
+ .catch( handleErrors );
+ }, []);
+
+ const {
+ mailchimp_api_key: mailchimpAPIKey = '',
+ mjml_api_key: mjmlApplicationId = '',
+ mjml_api_secret: mjmlAPISecret = '',
+ } = keys;
+ return (
+
+
+
+
{ __( 'Enter your Mailchimp API key', 'newspack-newsletters' ) }
+ { errors.newspack_newsletters_invalid_keys_mailchimp && (
+
{ errors.newspack_newsletters_invalid_keys_mailchimp }
+ ) }
+
setKeys( { ...keys, mailchimp_api_key: value } ) }
+ disabled={ inFlight }
+ onKeyDown={ event => {
+ if ( ENTER === event.keyCode ) {
+ event.preventDefault();
+ commitSettings();
+ }
+ } }
+ />
+ { inFlight && }
+
+ { __( 'About Mailchimp API keys', 'newspack-newsletters' ) }
+
+ { __( 'Enter your MJML API keys', 'newspack-newsletters' ) }
+ { errors.newspack_newsletters_invalid_keys_mjml && (
+ { errors.newspack_newsletters_invalid_keys_mjml }
+ ) }
+ setKeys( { ...keys, mjml_api_key: value } ) }
+ disabled={ inFlight }
+ onKeyDown={ event => {
+ if ( ENTER === event.keyCode ) {
+ event.preventDefault();
+ commitSettings();
+ }
+ } }
+ />
+ { inFlight && }
+ setKeys( { ...keys, mjml_api_secret: value } ) }
+ disabled={ inFlight }
+ onKeyDown={ event => {
+ if ( ENTER === event.keyCode ) {
+ event.preventDefault();
+ commitSettings();
+ }
+ } }
+ />
+ { inFlight && }
+
+ { __( 'Request MJML API keys', 'newspack-newsletters' ) }
+
+
+
+
+
+ );
+};
diff --git a/src/components/template-modal/screens/template-picker/index.js b/src/components/template-modal/screens/template-picker/index.js
new file mode 100644
index 000000000..5d264089d
--- /dev/null
+++ b/src/components/template-modal/screens/template-picker/index.js
@@ -0,0 +1,71 @@
+/**
+ * WordPress dependencies
+ */
+import { parse } from '@wordpress/blocks';
+import { Fragment, useState } from '@wordpress/element';
+import { Button } from '@wordpress/components';
+import { __, sprintf } from '@wordpress/i18n';
+import { BlockPreview } from '@wordpress/block-editor';
+import { ENTER, SPACE } from '@wordpress/keycodes';
+
+export default ( { onInsertTemplate, templates } ) => {
+ const [ selectedTemplate, setSelectedTemplate ] = useState( 0 );
+ const generateBlockPreview = () => {
+ return templates && templates[ selectedTemplate ]
+ ? parse( templates[ selectedTemplate ].content )
+ : null;
+ };
+ const blockPreview = generateBlockPreview();
+
+ return (
+
+
+
+
+ { ( templates || [] ).map( ( { title, content }, index ) => (
+
setSelectedTemplate( index ) }
+ onKeyDown={ event => {
+ if ( ENTER === event.keyCode || SPACE === event.keyCode ) {
+ event.preventDefault();
+ setSelectedTemplate( index );
+ }
+ } }
+ role="button"
+ tabIndex="0"
+ aria-label={ title }
+ >
+
+
+
+
{ title }
+
+ ) ) }
+
+
+
+
+ { blockPreview && blockPreview.length > 0 ? (
+
+ ) : (
+
{ __( 'Select a layout to preview.', 'newspack-newsletters' ) }
+ ) }
+
+
+ { selectedTemplate !== null && (
+
+ ) }
+
+ );
+};
diff --git a/src/components/template-modal/style.scss b/src/components/template-modal/style.scss
index 65c3951ad..a991831e9 100644
--- a/src/components/template-modal/style.scss
+++ b/src/components/template-modal/style.scss
@@ -62,6 +62,9 @@
top: 12px;
z-index: 11;
}
+ .error {
+ color: red;
+ }
}
&__patterns {
diff --git a/src/editor/index.js b/src/editor/index.js
index 654211baa..96adebf4c 100644
--- a/src/editor/index.js
+++ b/src/editor/index.js
@@ -59,8 +59,10 @@ const NewsletterEdit = ( {
const templates =
window && window.newspack_newsletters_data && window.newspack_newsletters_data.templates;
- const [ selectedTemplate, setSelectedTemplate ] = useState( 0 );
const [ insertedTemplate, setInserted ] = useState( null );
+ const [ hasKeys, setHasKeys ] = useState(
+ window && window.newspack_newsletters_data && window.newspack_newsletters_data.has_keys
+ );
const handleTemplateInsertion = templateIndex => {
const template = templates[ templateIndex ];
@@ -73,16 +75,16 @@ const NewsletterEdit = ( {
setInserted( templateIndex );
setTimeout( savePost, 1 );
};
-
const isDisplayingTemplateModal =
- isEditedPostEmpty && templates && templates.length && insertedTemplate === null;
+ ! hasKeys ||
+ ( isEditedPostEmpty && templates && templates.length && insertedTemplate === null );
return isDisplayingTemplateModal ? (
setHasKeys( status ) }
/>
) : (