From e337f4c8947e72866119dc673a0781b22de7bfbf Mon Sep 17 00:00:00 2001 From: Dmitry Merkushin Date: Thu, 18 Apr 2024 14:55:27 -0600 Subject: [PATCH] Fix preview links and button for emails (#7590) --- .../email-preview-button.js | 90 ------------- .../email-preview-button.scss | 7 -- .../email-preview-button.test.js | 118 ------------------ .../emails/email-preview-button/index.js | 13 -- changelog/fix-email-preview-link | 4 + .../internal/emails/class-email-preview.php | 45 ++++--- .../emails/test-class-email-preview.php | 80 +++++------- webpack.config.js | 2 - 8 files changed, 59 insertions(+), 300 deletions(-) delete mode 100644 assets/admin/emails/email-preview-button/email-preview-button.js delete mode 100644 assets/admin/emails/email-preview-button/email-preview-button.scss delete mode 100644 assets/admin/emails/email-preview-button/email-preview-button.test.js delete mode 100644 assets/admin/emails/email-preview-button/index.js create mode 100644 changelog/fix-email-preview-link diff --git a/assets/admin/emails/email-preview-button/email-preview-button.js b/assets/admin/emails/email-preview-button/email-preview-button.js deleted file mode 100644 index 1fb3a97ddb..0000000000 --- a/assets/admin/emails/email-preview-button/email-preview-button.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * WordPress dependencies - */ -import { useSelect, useDispatch } from '@wordpress/data'; -import { useState, useEffect, createPortal } from '@wordpress/element'; -import { Button } from '@wordpress/components'; -import { store as editorStore } from '@wordpress/editor'; -import { __ } from '@wordpress/i18n'; - -/** - * The email preview button component. - * - * @return {Object|null} A portal element or null if the container is not available. - */ -export const EmailPreviewButton = () => { - const { - postId, - isSaveable, - isAutosaveable, - isLocked, - isDraft, - previewLink, - } = useSelect( ( select ) => { - return { - postId: select( editorStore ).getCurrentPostId(), - isSaveable: select( editorStore ).isEditedPostSaveable(), - isAutosaveable: select( editorStore ).isEditedPostAutosaveable(), - isLocked: select( editorStore ).isPostLocked(), - isDraft: - [ 'draft', 'auto-draft' ].indexOf( - select( editorStore ).getEditedPostAttribute( 'status' ) - ) !== -1, - previewLink: window.sensei_email_preview.link, - }; - } ); - const { savePost, autosave } = useDispatch( editorStore ); - const [ isSaving, setIsSaving ] = useState( false ); - const [ container, setContainer ] = useState( null ); - - useEffect( () => { - setContainer( - document.querySelector( '.block-editor-post-preview__dropdown' ) - ); - }, [] ); - - if ( ! container ) { - return null; - } - - /** - * Open the preview in a new window. Triggers an autosave when needed. - * - * @param {MouseEvent} event - * @return {Promise} - */ - const openPreviewWindow = async ( event ) => { - event.preventDefault(); - - if ( isAutosaveable && ! isLocked ) { - setIsSaving( true ); - - if ( isDraft ) { - await savePost( { isPreview: true } ); - } else { - await autosave( { isPreview: true } ); - } - - setIsSaving( false ); - } - - window.open( event.target.href, 'sensei-email-preview-' + postId ); - }; - - return createPortal( - , - container - ); -}; - -export default EmailPreviewButton; diff --git a/assets/admin/emails/email-preview-button/email-preview-button.scss b/assets/admin/emails/email-preview-button/email-preview-button.scss deleted file mode 100644 index 9181b2db8c..0000000000 --- a/assets/admin/emails/email-preview-button/email-preview-button.scss +++ /dev/null @@ -1,7 +0,0 @@ -.block-editor-post-preview__button-toggle { - display: none; -} - -.sensei-email-preview-button.is-busy.is-tertiary { - background-image: linear-gradient(-45deg, #fafafa 33%, #e0e0e0 33%, #e0e0e0 70%, #fafafa 70%); -} diff --git a/assets/admin/emails/email-preview-button/email-preview-button.test.js b/assets/admin/emails/email-preview-button/email-preview-button.test.js deleted file mode 100644 index 0240ecfa71..0000000000 --- a/assets/admin/emails/email-preview-button/email-preview-button.test.js +++ /dev/null @@ -1,118 +0,0 @@ -/** - * External dependencies - */ -import { render, screen, act } from '@testing-library/react'; -import userEvent from '@testing-library/user-event'; - -/** - * WordPress dependencies - */ -import { useSelect, useDispatch } from '@wordpress/data'; - -/** - * Internal dependencies - */ -import { EmailPreviewButton } from './email-preview-button'; - -jest.mock( '@wordpress/data' ); - -const defaults = { - previewLink: 'https://example.com/', - postId: 1, - isSaveable: true, - isAutosaveable: true, - isLocked: false, - isDraft: false, -}; - -useSelect.mockReturnValue( defaults ); -useDispatch.mockReturnValue( { autosave: jest.fn(), savePost: jest.fn() } ); - -describe( '', () => { - const { getByText, queryByText } = screen; - - beforeEach( () => { - document.body.innerHTML = `
`; - } ); - - it( 'Should display the preview button', async () => { - render( ); - - expect( getByText( 'Preview' ) ).toBeInTheDocument(); - } ); - - it( "Shouldn't display the preview button when the container is missing", async () => { - document.body.innerHTML = ''; - - render( ); - - expect( queryByText( 'Preview' ) ).toBeNull(); - } ); - - it( 'Should open a new window when the button is clicked', async () => { - global.open = jest.fn(); - - render( ); - - await act( async () => { - userEvent.click( getByText( 'Preview' ) ); - } ); - - expect( global.open ).toBeCalledWith( - defaults.previewLink, - 'sensei-email-preview-' + defaults.postId - ); - } ); - - it( 'Should call autosave when the post is not a draft', async () => { - const autosaveMock = jest.fn(); - - useDispatch.mockReturnValue( { autosave: autosaveMock } ); - - render( ); - - await act( async () => { - userEvent.click( getByText( 'Preview' ) ); - } ); - - expect( autosaveMock ).toBeCalled(); - } ); - - it( 'Should call savePost when the post is a draft', async () => { - const savePostMock = jest.fn(); - - useSelect.mockReturnValue( { ...defaults, isDraft: true } ); - useDispatch.mockReturnValue( { savePost: savePostMock } ); - - render( ); - - await act( async () => { - userEvent.click( getByText( 'Preview' ) ); - } ); - - expect( savePostMock ).toBeCalled(); - } ); - - it( 'Should not save when not autosaveable', async () => { - const autosaveMock = jest.fn(); - - useSelect.mockReturnValue( { ...defaults, isAutosaveable: false } ); - useDispatch.mockReturnValue( { autosave: autosaveMock } ); - - render( ); - - await act( async () => { - userEvent.click( getByText( 'Preview' ) ); - } ); - - expect( autosaveMock ).not.toBeCalled(); - } ); - - it( 'Should be disabled if not saveable', async () => { - useSelect.mockReturnValue( { ...defaults, isSaveable: false } ); - - render( ); - - expect( getByText( 'Preview' ) ).toBeDisabled(); - } ); -} ); diff --git a/assets/admin/emails/email-preview-button/index.js b/assets/admin/emails/email-preview-button/index.js deleted file mode 100644 index 58106be8a1..0000000000 --- a/assets/admin/emails/email-preview-button/index.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * WordPress dependencies - */ -import { registerPlugin } from '@wordpress/plugins'; - -/** - * Internal dependencies - */ -import EmailPreviewButton from './email-preview-button'; - -registerPlugin( 'sensei-email-preview-plugin', { - render: EmailPreviewButton, -} ); diff --git a/changelog/fix-email-preview-link b/changelog/fix-email-preview-link new file mode 100644 index 0000000000..9ed53e3c36 --- /dev/null +++ b/changelog/fix-email-preview-link @@ -0,0 +1,4 @@ +Significance: patch +Type: fixed + +Fix preview email button and links diff --git a/includes/internal/emails/class-email-preview.php b/includes/internal/emails/class-email-preview.php index 784b6b29c4..e06e030090 100644 --- a/includes/internal/emails/class-email-preview.php +++ b/includes/internal/emails/class-email-preview.php @@ -56,7 +56,8 @@ public function __construct( Email_Sender $email_sender, Sensei_Assets $assets ) */ public function init(): void { add_action( 'template_redirect', [ $this, 'render_preview' ] ); - add_action( 'admin_enqueue_scripts', [ $this, 'register_admin_scripts' ] ); + add_filter( 'preview_post_link', [ $this, 'filter_preview_link' ], 10, 2 ); + add_filter( 'post_type_link', [ $this, 'filter_preview_link' ], 10, 2 ); } /** @@ -83,29 +84,6 @@ public function render_preview(): void { } } - /** - * Register and enqueue scripts and styles that are needed in the backend. - * - * @internal - */ - public function register_admin_scripts(): void { - $screen = get_current_screen(); - if ( ! $screen || Email_Post_Type::POST_TYPE !== $screen->id ) { - return; - } - - $this->assets->enqueue( 'sensei-email-preview-button', 'admin/emails/email-preview-button/index.js', [], true ); - $this->assets->enqueue( 'sensei-email-preview-button', 'admin/emails/email-preview-button/email-preview-button.css' ); - - wp_localize_script( - 'sensei-email-preview-button', - 'sensei_email_preview', - [ - 'link' => self::get_preview_link( get_the_ID() ), - ] - ); - } - /** * Get the preview link. * @@ -217,10 +195,29 @@ private function validate_request(): void { wp_die( esc_html__( 'Invalid request', 'sensei-lms' ) ); } + // phpcs:ignore WordPress.WP.Capabilities.Unknown if ( ! current_user_can( 'manage_sensei' ) ) { wp_die( esc_html__( 'Insufficient permissions', 'sensei-lms' ) ); } check_admin_referer( 'preview-email-post_' . $post->ID ); } + + /** + * Filter the preview link. + * + * @internal + * + * @param string $link The preview link. + * @param WP_Post $post The post object. + * + * @return string + */ + public function filter_preview_link( $link, $post ): string { + if ( Email_Post_Type::POST_TYPE !== $post->post_type ) { + return $link; + } + + return str_replace( '&', '&', $this->get_preview_link( $post->ID ) ); + } } diff --git a/tests/unit-tests/internal/emails/test-class-email-preview.php b/tests/unit-tests/internal/emails/test-class-email-preview.php index 37e9ef00b6..13268d91b4 100644 --- a/tests/unit-tests/internal/emails/test-class-email-preview.php +++ b/tests/unit-tests/internal/emails/test-class-email-preview.php @@ -7,6 +7,7 @@ use Sensei_Assets; use Sensei_Factory; use Sensei_Test_Login_Helpers; +use WP_Post; use WPDieException; /** @@ -41,7 +42,7 @@ public function testInit_WhenCalled_AddsRenderPreviewHook() { $this->assertSame( 10, $priority ); } - public function testInit_WhenCalled_AddsRegisterAdminScriptsHook() { + public function testInit_WhenCalled_AddsPreviewPostLinkHook() { /* Arrange. */ $email_preview = new Email_Preview( $this->email_sender, $this->assets ); @@ -49,7 +50,19 @@ public function testInit_WhenCalled_AddsRegisterAdminScriptsHook() { $email_preview->init(); /* Assert. */ - $priority = has_action( 'admin_enqueue_scripts', [ $email_preview, 'register_admin_scripts' ] ); + $priority = has_action( 'preview_post_link', [ $email_preview, 'filter_preview_link' ] ); + $this->assertSame( 10, $priority ); + } + + public function testInit_WhenCalled_AddsPostTypeLinkHook() { + /* Arrange. */ + $email_preview = new Email_Preview( $this->email_sender, $this->assets ); + + /* Act. */ + $email_preview->init(); + + /* Assert. */ + $priority = has_action( 'post_type_link', [ $email_preview, 'filter_preview_link' ] ); $this->assertSame( 10, $priority ); } @@ -227,64 +240,39 @@ public function testRenderPreview_WhenRenderingEmail_RendersEmailBody() { $this->assertSame( 'content', $content ); } - public function testRegisterAdminScripts_WhenNoScreen_DoesNothing() { + public function testGetPreviewLink_WhenCalled_ReturnsThePreviewLink() { /* Arrange. */ - $email_preview = new Email_Preview( $this->email_sender, $this->assets ); - - /* Assert. */ - $this->assets - ->expects( $this->never() ) - ->method( 'enqueue' ); + $post_id = 1; /* Act. */ - $email_preview->register_admin_scripts(); - } - - public function testRegisterAdminScripts_WhenNotOnTheEmailEditScreen_DoesNothing() { - /* Arrange. */ - $email_preview = new Email_Preview( $this->email_sender, $this->assets ); - - set_current_screen( 'test' ); + $link = Email_Preview::get_preview_link( $post_id ); /* Assert. */ - $this->assets - ->expects( $this->never() ) - ->method( 'enqueue' ); - - /* Act. */ - $email_preview->register_admin_scripts(); + $this->assertSame( + wp_nonce_url( get_home_url() . "?sensei_email_preview_id=$post_id", 'preview-email-post_' . $post_id ), + $link + ); } - public function testRegisterAdminScripts_WhenOnTheEmailEditScreen_EnqueuesTheScript() { + public function testFilterPreviewLink_WhenCalled_ReturnsThePreviewLink() { /* Arrange. */ $email_preview = new Email_Preview( $this->email_sender, $this->assets ); - - set_current_screen( 'sensei_email' ); - - /* Assert. */ - $this->assets - ->expects( $this->exactly( 2 ) ) - ->method( 'enqueue' ) - ->withConsecutive( - [ 'sensei-email-preview-button', 'admin/emails/email-preview-button/index.js', [], true ], - [ 'sensei-email-preview-button', 'admin/emails/email-preview-button/email-preview-button.css' ] - ); - - /* Act. */ - $email_preview->register_admin_scripts(); - } - - public function testGetPreviewLink_WhenCalled_ReturnsThePreviewLink() { - /* Arrange. */ - $post_id = 1; + $link = 'http://example.com/?p=1'; + $post = new WP_Post( + (object) array( + 'ID' => 1, + 'post_type' => 'sensei_email', + ) + ); /* Act. */ - $link = Email_Preview::get_preview_link( $post_id ); + $filtered_link = $email_preview->filter_preview_link( $link, $post ); /* Assert. */ + $nonce = wp_create_nonce( 'preview-email-post_1' ); $this->assertSame( - wp_nonce_url( get_home_url() . "?sensei_email_preview_id=$post_id", 'preview-email-post_' . $post_id ), - $link + get_home_url() . '?sensei_email_preview_id=1&_wpnonce=' . $nonce, + $filtered_link ); } } diff --git a/webpack.config.js b/webpack.config.js index 4e59e6c9ee..efb65098d6 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -102,8 +102,6 @@ const files = [ 'admin/students/student-bulk-action-button/index.js', 'admin/students/student-bulk-action-button/student-bulk-action-button.scss', 'admin/students/student-modal/student-modal.scss', - 'admin/emails/email-preview-button/index.js', - 'admin/emails/email-preview-button/email-preview-button.scss', 'css/block-patterns.scss', 'css/page-block-patterns.scss', 'css/tools.scss',