From 2bb3ff20a8cfe8cad01ce38f60270859de94a898 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 10 Feb 2026 11:40:50 +0800 Subject: [PATCH 1/2] Classic Editor (TinyMCE/QuickTags): Use REST API --- admin/class-convertkit-admin-tinymce.php | 64 +++++++++++++++++------- includes/class-wp-convertkit.php | 2 +- resources/backend/js/editor.js | 5 +- resources/backend/js/quicktags.js | 5 +- 4 files changed, 52 insertions(+), 24 deletions(-) diff --git a/admin/class-convertkit-admin-tinymce.php b/admin/class-convertkit-admin-tinymce.php index 2be0e35d8..178f83f11 100644 --- a/admin/class-convertkit-admin-tinymce.php +++ b/admin/class-convertkit-admin-tinymce.php @@ -21,7 +21,7 @@ class ConvertKit_Admin_TinyMCE { public function __construct() { // Outputs the TinyMCE and QuickTag Modal. - add_action( 'wp_ajax_convertkit_admin_tinymce_output_modal', array( $this, 'output_modal' ) ); + add_action( 'rest_api_init', array( $this, 'register_routes' ) ); // Add filters to register QuickTag Plugins. add_action( 'admin_enqueue_scripts', array( $this, 'register_quicktags' ) ); // WordPress Admin. @@ -34,29 +34,57 @@ public function __construct() { } + /** + * Register REST API routes. + * + * @since 3.1.8 + */ + public function register_routes() { + + // Register route to return all blocks registered by the Plugin. + register_rest_route( + 'kit/v1', + '/tinymce/output-modal', + array( + 'methods' => WP_REST_Server::CREATABLE, + 'args' => array( + 'shortcode' => array( + 'required' => true, + 'sanitize_callback' => 'sanitize_text_field', + ), + 'editor_type' => array( + 'required' => true, + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'callback' => function ( $request ) { + ob_start(); + $this->output_modal( $request['shortcode'], $request['editor_type'] ); + return ob_get_clean(); + }, + + // Only refresh resources for users who can edit posts. + 'permission_callback' => function () { + return current_user_can( 'edit_posts' ); + }, + ) + ); + + } + /** * Loads the view for a shortcode's modal in the TinyMCE and Text Editors. * * @since 1.9.6 + * + * @param string $shortcode_name Shortcode Name. + * @param string $editor_type Editor Type (tinymce|quicktags). */ - public function output_modal() { - - // Check nonce. - check_ajax_referer( 'convertkit_admin_tinymce', 'nonce' ); + public function output_modal( $shortcode_name, $editor_type ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed // Get shortcodes. $shortcodes = convertkit_get_shortcodes(); - // Bail if no shortcode or editor type is specified. - if ( ! isset( $_REQUEST['shortcode'] ) || ! isset( $_REQUEST['editor_type'] ) ) { - require_once CONVERTKIT_PLUGIN_PATH . '/views/backend/tinymce/modal-missing.php'; - die(); - } - - // Get requested shortcode name. - $shortcode_name = sanitize_text_field( wp_unslash( $_REQUEST['shortcode'] ) ); - $editor_type = sanitize_text_field( wp_unslash( $_REQUEST['editor_type'] ) ); - // If the shortcode is not registered, return a view in the modal to tell the user. if ( ! isset( $shortcodes[ $shortcode_name ] ) ) { require_once CONVERTKIT_PLUGIN_PATH . '/views/backend/tinymce/modal-missing.php'; @@ -118,7 +146,8 @@ public function register_quicktags() { 'convertkit-admin-quicktags', 'convertkit_admin_tinymce', array( - 'nonce' => wp_create_nonce( 'convertkit_admin_tinymce' ), + 'ajaxurl' => rest_url( 'kit/v1/tinymce/output-modal' ), + 'nonce' => wp_create_nonce( 'wp_rest' ), ) ); @@ -160,7 +189,8 @@ public function register_tinymce_plugins( $plugins ) { 'convertkit-admin-editor', 'convertkit_admin_tinymce', array( - 'nonce' => wp_create_nonce( 'convertkit_admin_tinymce' ), + 'ajaxurl' => rest_url( 'kit/v1/tinymce/output-modal' ), + 'nonce' => wp_create_nonce( 'wp_rest' ), ) ); diff --git a/includes/class-wp-convertkit.php b/includes/class-wp-convertkit.php index ecafc5792..3dbb8f8b6 100644 --- a/includes/class-wp-convertkit.php +++ b/includes/class-wp-convertkit.php @@ -96,7 +96,6 @@ private function initialize_admin() { $this->classes['admin_setup_wizard_landing_page'] = new ConvertKit_Admin_Setup_Wizard_Landing_Page(); $this->classes['admin_setup_wizard_plugin'] = new ConvertKit_Admin_Setup_Wizard_Plugin(); $this->classes['admin_setup_wizard_restrict_content'] = new ConvertKit_Admin_Setup_Wizard_Restrict_Content(); - $this->classes['admin_tinymce'] = new ConvertKit_Admin_TinyMCE(); /** * Initialize integration classes for the WordPress Administration interface. @@ -183,6 +182,7 @@ private function initialize_frontend() { private function initialize_global() { $this->classes['admin_notices'] = new ConvertKit_Admin_Notices(); + $this->classes['admin_tinymce'] = new ConvertKit_Admin_TinyMCE(); $this->classes['admin_refresh_resources'] = new ConvertKit_Admin_Refresh_Resources(); $this->classes['blocks_convertkit_broadcasts'] = new ConvertKit_Block_Broadcasts(); $this->classes['blocks_convertkit_content'] = new ConvertKit_Block_Content(); diff --git a/resources/backend/js/editor.js b/resources/backend/js/editor.js index 0d9fa9e46..eae530805 100644 --- a/resources/backend/js/editor.js +++ b/resources/backend/js/editor.js @@ -55,14 +55,13 @@ function convertKitTinyMCERegisterPlugin(block) { }); // Perform an AJAX call to load the modal's view. - fetch(ajaxurl, { + fetch(convertkit_admin_tinymce.ajaxurl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', + 'X-WP-Nonce': convertkit_admin_tinymce.nonce, }, body: new URLSearchParams({ - action: 'convertkit_admin_tinymce_output_modal', - nonce: convertkit_admin_tinymce.nonce, editor_type: 'tinymce', shortcode: block.name, }), diff --git a/resources/backend/js/quicktags.js b/resources/backend/js/quicktags.js index 921fb45fa..2bf45c5d2 100644 --- a/resources/backend/js/quicktags.js +++ b/resources/backend/js/quicktags.js @@ -21,14 +21,13 @@ for (const block in convertkit_quicktags) { function convertKitQuickTagRegister(block) { QTags.addButton('convertkit-' + block.name, block.title, function () { // Perform an AJAX call to load the modal's view. - fetch(ajaxurl, { + fetch(convertkit_admin_tinymce.ajaxurl, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', + 'X-WP-Nonce': convertkit_admin_tinymce.nonce, }, body: new URLSearchParams({ - action: 'convertkit_admin_tinymce_output_modal', - nonce: convertkit_admin_tinymce.nonce, editor_type: 'quicktags', shortcode: block.name, }), From a6dba83fb9a994e9fa5f16543fcff07ea765d7c7 Mon Sep 17 00:00:00 2001 From: Tim Carr Date: Tue, 10 Feb 2026 12:30:21 +0800 Subject: [PATCH 2/2] Broadcasts Block/Shortcode: Use REST API --- .../class-convertkit-block-broadcasts.php | 115 ++++++++++++++---- resources/frontend/js/broadcasts.js | 4 - 2 files changed, 90 insertions(+), 29 deletions(-) diff --git a/includes/blocks/class-convertkit-block-broadcasts.php b/includes/blocks/class-convertkit-block-broadcasts.php index 3b987df7e..ac7abb3fd 100644 --- a/includes/blocks/class-convertkit-block-broadcasts.php +++ b/includes/blocks/class-convertkit-block-broadcasts.php @@ -31,9 +31,78 @@ public function __construct() { add_action( 'convertkit_gutenberg_enqueue_scripts_editor_and_frontend', array( $this, 'enqueue_scripts' ) ); add_action( 'convertkit_gutenberg_enqueue_styles_editor_and_frontend', array( $this, 'enqueue_styles' ) ); - // Render Broadcasts block via AJAX. - add_action( 'wp_ajax_nopriv_convertkit_broadcasts_render', array( $this, 'render_ajax' ) ); - add_action( 'wp_ajax_convertkit_broadcasts_render', array( $this, 'render_ajax' ) ); + // Register REST API routes. + add_action( 'rest_api_init', array( $this, 'register_routes' ) ); + + } + + /** + * Register REST API routes. + * + * @since 3.1.8 + */ + public function register_routes() { + + register_rest_route( + 'kit/v1', + '/broadcasts/render', + array( + 'methods' => WP_REST_Server::CREATABLE, + 'args' => array( + 'date_format' => array( + 'default' => $this->get_default_value( 'date_format' ), + 'sanitize_callback' => 'sanitize_text_field', + ), + 'display_image' => array( + 'default' => $this->get_default_value( 'display_image' ), + 'sanitize_callback' => 'absint', + ), + 'display_description' => array( + 'default' => $this->get_default_value( 'display_description' ), + 'sanitize_callback' => 'absint', + ), + 'display_read_more' => array( + 'default' => $this->get_default_value( 'display_read_more' ), + 'sanitize_callback' => 'absint', + ), + 'read_more_label' => array( + 'default' => $this->get_default_value( 'read_more_label' ), + 'sanitize_callback' => 'sanitize_text_field', + ), + 'limit' => array( + 'default' => $this->get_default_value( 'limit' ), + 'sanitize_callback' => 'absint', + ), + 'page' => array( + 'default' => $this->get_default_value( 'page' ), + 'sanitize_callback' => 'absint', + ), + 'paginate' => array( + 'default' => $this->get_default_value( 'paginate' ), + 'sanitize_callback' => 'absint', + ), + 'paginate_label_next' => array( + 'default' => $this->get_default_value( 'paginate_label_next' ), + 'sanitize_callback' => 'sanitize_text_field', + ), + 'paginate_label_prev' => array( + 'default' => $this->get_default_value( 'paginate_label_prev' ), + 'sanitize_callback' => 'sanitize_text_field', + ), + 'link_color' => array( + 'default' => $this->get_default_value( 'link_color' ), + 'sanitize_callback' => 'sanitize_text_field', + ), + ), + 'callback' => function ( $request ) { + $html = $this->render_ajax( $request ); + return rest_ensure_response( array( 'data' => $html ) ); + }, + + // No authentication required, as this is on the frontend site. + 'permission_callback' => '__return_true', + ) + ); } @@ -52,11 +121,8 @@ public function enqueue_scripts() { 'convertkit-' . $this->get_name(), 'convertkit_broadcasts', array( - // WordPress AJAX URL endpoint. - 'ajax_url' => admin_url( 'admin-ajax.php' ), - - // AJAX action registered in __construct(). - 'action' => 'convertkit_broadcasts_render', + // REST API URL endpoint. + 'ajax_url' => rest_url( 'kit/v1/broadcasts/render' ), // Whether debugging is enabled. 'debug' => $settings->debug_enabled(), @@ -538,25 +604,25 @@ public function render( $atts ) { * when requested via AJAX. * * @since 1.9.7.6 + * + * @param WP_REST_Request $request The REST request. + * @return string */ - public function render_ajax() { - - // Check nonce. - check_ajax_referer( 'convertkit-broadcasts', 'nonce' ); + public function render_ajax( $request ) { // Build attributes array. $atts = array( - 'date_format' => ( isset( $_REQUEST['date_format'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['date_format'] ) ) : $this->get_default_value( 'date_format' ) ), - 'display_image' => ( isset( $_REQUEST['display_image'] ) ? absint( $_REQUEST['display_image'] ) : $this->get_default_value( 'display_image' ) ), - 'display_description' => ( isset( $_REQUEST['display_description'] ) ? absint( $_REQUEST['display_description'] ) : $this->get_default_value( 'display_description' ) ), - 'display_read_more' => ( isset( $_REQUEST['display_read_more'] ) ? absint( $_REQUEST['display_read_more'] ) : $this->get_default_value( 'display_read_more' ) ), - 'read_more_label' => ( isset( $_REQUEST['read_more_label'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['read_more_label'] ) ) : $this->get_default_value( 'read_more_label' ) ), - 'limit' => ( isset( $_REQUEST['limit'] ) ? absint( $_REQUEST['limit'] ) : $this->get_default_value( 'limit' ) ), - 'page' => ( isset( $_REQUEST['page'] ) ? absint( $_REQUEST['page'] ) : $this->get_default_value( 'page' ) ), - 'paginate' => ( isset( $_REQUEST['paginate'] ) ? absint( $_REQUEST['paginate'] ) : $this->get_default_value( 'paginate' ) ), - 'paginate_label_next' => ( isset( $_REQUEST['paginate_label_next'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['paginate_label_next'] ) ) : $this->get_default_value( 'paginate_label_next' ) ), - 'paginate_label_prev' => ( isset( $_REQUEST['paginate_label_prev'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['paginate_label_prev'] ) ) : $this->get_default_value( 'paginate_label_prev' ) ), - 'link_color' => ( isset( $_REQUEST['link_color'] ) ? sanitize_text_field( wp_unslash( $_REQUEST['link_color'] ) ) : $this->get_default_value( 'link_color' ) ), + 'date_format' => $request->get_param( 'date_format' ), + 'display_image' => $request->get_param( 'display_image' ), + 'display_description' => $request->get_param( 'display_description' ), + 'display_read_more' => $request->get_param( 'display_read_more' ), + 'read_more_label' => $request->get_param( 'read_more_label' ), + 'limit' => $request->get_param( 'limit' ), + 'page' => $request->get_param( 'page' ), + 'paginate' => $request->get_param( 'paginate' ), + 'paginate_label_next' => $request->get_param( 'paginate_label_next' ), + 'paginate_label_prev' => $request->get_param( 'paginate_label_prev' ), + 'link_color' => $request->get_param( 'link_color' ), ); // Parse attributes, defining fallback defaults if required @@ -580,8 +646,7 @@ public function render_ajax() { */ $html = apply_filters( 'convertkit_block_broadcasts_render_ajax', $html, $atts ); - // Send HTML as response. - wp_send_json_success( $html ); + return $html; } diff --git a/resources/frontend/js/broadcasts.js b/resources/frontend/js/broadcasts.js index ae736ecb8..36c327ccb 100644 --- a/resources/frontend/js/broadcasts.js +++ b/resources/frontend/js/broadcasts.js @@ -33,7 +33,6 @@ document.addEventListener('DOMContentLoaded', function () { paginate_label_next: blockContainer.dataset.paginateLabelNext, link_color: blockContainer.dataset.linkColor, page: e.target.dataset.page, - nonce: e.target.dataset.nonce, }; convertKitBroadcastsRender(blockContainer, atts); @@ -51,9 +50,6 @@ document.addEventListener('DOMContentLoaded', function () { * @param {Object} atts Block attributes */ function convertKitBroadcastsRender(blockContainer, atts) { - // Append action. - atts.action = convertkit_broadcasts.action; - if (convertkit_broadcasts.debug) { console.log('convertKitBroadcastsRender()'); console.log(atts);