diff --git a/admin/class-convertkit-admin-post.php b/admin/class-convertkit-admin-post.php index b36a5cbe9..8b9cee2f7 100644 --- a/admin/class-convertkit-admin-post.php +++ b/admin/class-convertkit-admin-post.php @@ -8,8 +8,8 @@ /** * Registers a metabox on Posts, Pages and public facing Custom Post Types - * and saves its settings when the Post is saved in the WordPress Administration - * interface. + * that do not use the block editor, saving settings when the Post is saved + * in the WordPress Administration interface. * * @package ConvertKit * @author ConvertKit @@ -27,7 +27,7 @@ public function __construct() { add_filter( 'views_edit-page', array( $this, 'output_wp_list_table_buttons' ) ); add_action( 'post_submitbox_misc_actions', array( $this, 'output_pre_publish_actions' ) ); - add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ) ); + add_action( 'add_meta_boxes', array( $this, 'add_meta_boxes' ), 10, 2 ); add_action( 'save_post', array( $this, 'save_post_meta' ) ); } @@ -181,9 +181,10 @@ public function output_pre_publish_actions( $post ) { * * @since 1.9.6 * - * @param string $post_type Post Type. + * @param string $post_type Post Type. + * @param WP_Post $post Post. */ - public function add_meta_boxes( $post_type ) { + public function add_meta_boxes( $post_type, $post ) { // Don't register the meta box if this Post Type isn't supported. $supported_post_types = convertkit_get_supported_post_types(); @@ -191,6 +192,11 @@ public function add_meta_boxes( $post_type ) { return; } + // Don't register the meta box if the block editor is being used, as register_post_meta() handles saving post meta. + if ( function_exists( 'use_block_editor_for_post' ) && use_block_editor_for_post( $post ) ) { + return; + } + // Register Meta Box. add_meta_box( 'wp-convertkit-meta-box', __( 'Kit', 'convertkit' ), array( $this, 'display_meta_box' ), $post_type, 'normal' ); @@ -261,6 +267,11 @@ public function save_post_meta( $post_id ) { return; } + // Bail if the block editor is being used, as register_post_meta() handles saving post meta. + if ( function_exists( 'use_block_editor_for_post' ) && use_block_editor_for_post( $post_id ) ) { + return; + } + // Bail if no nonce field exists. if ( ! isset( $_POST['wp-convertkit-save-meta-nonce'] ) ) { return; diff --git a/includes/class-convertkit-gutenberg.php b/includes/class-convertkit-gutenberg.php index 1c51589cf..eaa557449 100644 --- a/includes/class-convertkit-gutenberg.php +++ b/includes/class-convertkit-gutenberg.php @@ -32,6 +32,9 @@ public function __construct() { // Register Gutenberg Blocks. add_action( 'init', array( $this, 'add_blocks' ) ); + // Register Gutenberg Plugin Sidebars. + add_action( 'init', array( $this, 'add_plugin_sidebars' ) ); + // Register REST API routes. add_action( 'rest_api_init', array( $this, 'register_routes' ) ); @@ -187,6 +190,63 @@ public function add_blocks() { } + /** + * Registers post meta for any registered plugin sidebars using register_post_meta(), + * so data is saved when using the Gutenberg editor. + * + * @since 3.3.0 + */ + public function add_plugin_sidebars() { + + // Get plugin sidebars. + $plugin_sidebars = convertkit_get_plugin_sidebars(); + + // Bail if no plugin sidebars are available. + if ( ! count( $plugin_sidebars ) ) { + return; + } + + foreach ( $plugin_sidebars as $plugin_sidebar ) { + register_post_meta( + '', + $plugin_sidebar['meta_key'], + array( + 'show_in_rest' => array( + 'schema' => array( + 'type' => 'object', + 'properties' => $plugin_sidebar['attributes'], + ), + ), + 'single' => true, + 'type' => 'object', + 'default' => $plugin_sidebar['default_values'], + 'sanitize_callback' => function ( $value ) use ( $plugin_sidebar ) { + + // If the value is not an array, return the default values. + if ( ! is_array( $value ) ) { + return $plugin_sidebar['default_values']; + } + + // Iterate through the attributes and sanitize the value. + foreach ( $plugin_sidebar['attributes'] as $key => $attribute ) { + $value[ $key ] = sanitize_text_field( $value[ $key ] ?? $value['default'] ); + } + + // Return the sanitized value. + return $value; + + }, + 'auth_callback' => function () use ( $plugin_sidebar ) { + + return current_user_can( $plugin_sidebar['minimum_capability'] ); + + }, + ) + ); + } + + } + /** * Determines the block API version to use for registering blocks. * @@ -214,6 +274,7 @@ public function get_block_api_version() { return absint( $block_api_version ); } + /** * Enqueues scripts for Gutenberg blocks in the editor view. * @@ -233,13 +294,23 @@ public function enqueue_scripts() { $blocks = convertkit_get_blocks(); $block_formatters = convertkit_get_block_formatters(); $pre_publish_actions = convertkit_get_pre_publish_actions(); + $plugin_sidebars = convertkit_get_plugin_sidebars(); // Enqueue Gutenberg Javascript, and set the blocks data. wp_enqueue_script( 'convertkit-gutenberg', CONVERTKIT_PLUGIN_URL . 'resources/backend/js/gutenberg.js', array( 'jquery' ), CONVERTKIT_PLUGIN_VERSION, true ); wp_localize_script( 'convertkit-gutenberg', 'convertkit_blocks', $blocks ); + + // If pre-publish actions are available, set the data. if ( count( $pre_publish_actions ) ) { wp_localize_script( 'convertkit-gutenberg', 'convertkit_pre_publish_actions', $pre_publish_actions ); } + + // If plugin sidebars are available, set the data. + if ( count( $plugin_sidebars ) ) { + wp_localize_script( 'convertkit-gutenberg', 'convertkit_plugin_sidebars', $plugin_sidebars ); + } + + // Set the Gutenberg data. wp_localize_script( 'convertkit-gutenberg', 'convertkit_gutenberg', diff --git a/includes/class-wp-convertkit.php b/includes/class-wp-convertkit.php index 3dbb8f8b6..9e49cc858 100644 --- a/includes/class-wp-convertkit.php +++ b/includes/class-wp-convertkit.php @@ -196,6 +196,7 @@ private function initialize_global() { $this->classes['block_formatter_form_link'] = new ConvertKit_Block_Formatter_Form_Link(); $this->classes['block_formatter_product_link'] = new ConvertKit_Block_Formatter_Product_Link(); $this->classes['pre_publish_action_broadcast_export'] = new ConvertKit_Pre_Publish_Action_Broadcast_Export(); + $this->classes['plugin_sidebar_post_settings'] = new ConvertKit_Plugin_Sidebar_Post_Settings(); $this->classes['broadcasts_exporter'] = new ConvertKit_Broadcasts_Exporter(); $this->classes['broadcasts_importer'] = new ConvertKit_Broadcasts_Importer(); $this->classes['elementor'] = new ConvertKit_Elementor(); diff --git a/includes/functions.php b/includes/functions.php index f4f214ec1..8550c73d5 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -234,6 +234,30 @@ function convertkit_get_block_formatters() { } +/** + * Helper method to get registered plugin sidebars. + * + * @since 3.3.0 + * + * @return array Plugin sidebars + */ +function convertkit_get_plugin_sidebars() { + + $plugin_sidebars = array(); + + /** + * Registers plugin sidebars for the WordPress block editor. + * + * @since 3.3.0 + * + * @param array $plugin_sidebars Plugin sidebars. + */ + $plugin_sidebars = apply_filters( 'convertkit_plugin_sidebars', $plugin_sidebars ); + + return $plugin_sidebars; + +} + /** * Helper method to get registered pre-publish actions. * diff --git a/includes/plugin-sidebars/class-convertkit-plugin-sidebar-post-settings.php b/includes/plugin-sidebars/class-convertkit-plugin-sidebar-post-settings.php new file mode 100644 index 000000000..9e4d934aa --- /dev/null +++ b/includes/plugin-sidebars/class-convertkit-plugin-sidebar-post-settings.php @@ -0,0 +1,234 @@ + array( + 'type' => 'string', + 'default' => $this->get_default_value( 'form' ), + ), + 'landing_page' => array( + 'type' => 'string', + 'default' => $this->get_default_value( 'landing_page' ), + ), + 'tag' => array( + 'type' => 'string', + 'default' => $this->get_default_value( 'tag' ), + ), + 'restrict_content' => array( + 'type' => 'string', + 'default' => $this->get_default_value( 'restrict_content' ), + ), + ); + + } + + /** + * Returns this plugin sidebar's Fields + * + * @since 3.3.0 + * + * @return bool|array + */ + public function get_fields() { + + // Load resource classes. + $convertkit_forms = new ConvertKit_Resource_Forms( 'post_settings' ); + $convertkit_landing_pages = new ConvertKit_Resource_Landing_Pages( 'post_settings' ); + $convertkit_tags = new ConvertKit_Resource_Tags( 'post_settings' ); + $convertkit_products = new ConvertKit_Resource_Products( 'post_settings' ); + + // Get Forms. + $forms = array( + '-1' => esc_html__( 'Default', 'convertkit' ), + '0' => esc_html__( 'None', 'convertkit' ), + ); + if ( $convertkit_forms->exist() ) { + foreach ( $convertkit_forms->get() as $form ) { + // Legacy forms don't include a `format` key, so define them as inline. + $forms[ absint( $form['id'] ) ] = sprintf( + '%s [%s]', + sanitize_text_field( $form['name'] ), + ( ! empty( $form['format'] ) ? sanitize_text_field( $form['format'] ) : 'inline' ) + ); + } + } + + // Get Landing Pages. + $landing_pages = array( + '0' => esc_html__( 'None', 'convertkit' ), + ); + if ( $convertkit_landing_pages->exist() ) { + foreach ( $convertkit_landing_pages->get() as $landing_page ) { + $landing_pages[ absint( $landing_page['id'] ) ] = sanitize_text_field( $landing_page['name'] ); + } + } + + // Get Tags. + $tags = array( + '0' => esc_html__( 'None', 'convertkit' ), + ); + if ( $convertkit_tags->exist() ) { + foreach ( $convertkit_tags->get() as $tag ) { + $tags[ absint( $tag['id'] ) ] = sanitize_text_field( $tag['name'] ); + } + } + + // Get Products. + $restrict_content = array( + '0' => esc_html__( 'Don\'t restrict content to member-only', 'convertkit' ), + ); + if ( $convertkit_forms->exist() ) { + foreach ( $convertkit_forms->get() as $form ) { + // Legacy forms don't include a `format` key, so define them as inline. + $restrict_content[ 'form_' . absint( $form['id'] ) ] = sprintf( + '%s [%s]', + sanitize_text_field( $form['name'] ), + ( ! empty( $form['format'] ) ? sanitize_text_field( $form['format'] ) : 'inline' ) + ); + } + } + if ( $convertkit_tags->exist() ) { + foreach ( $convertkit_tags->get() as $tag ) { + $restrict_content[ 'tag_' . absint( $tag['id'] ) ] = sanitize_text_field( $tag['name'] ); + } + } + if ( $convertkit_products->exist() ) { + foreach ( $convertkit_products->get() as $product ) { + $restrict_content[ 'product_' . $product['id'] ] = sanitize_text_field( $product['name'] ); + } + } + + return array( + 'form' => array( + 'label' => __( 'Form', 'convertkit' ), + 'type' => 'select', + 'description' => array( + __( 'Default', 'convertkit' ) . ': ' . __( 'Uses the form specified on the settings page.', 'convertkit' ), + __( 'None', 'convertkit' ) . ': ' . __( 'do not display a form.', 'convertkit' ), + __( 'Any other option will display that form after the main content.', 'convertkit' ), + ), + 'values' => $forms, + ), + 'landing_page' => array( + 'label' => __( 'Landing Page', 'convertkit' ), + 'type' => 'select', + 'description' => __( 'Select a landing page to make it appear in place of this page.', 'convertkit' ), + 'values' => $landing_pages, + ), + 'tag' => array( + 'label' => __( 'Tag', 'convertkit' ), + 'type' => 'select', + 'description' => __( 'Select a tag to apply to visitors of this page who are subscribed. A visitor is deemed to be subscribed if they have clicked a link in an email to this site which includes their subscriber ID, or have entered their email address in a Kit Form on this site.', 'convertkit' ), + 'values' => $tags, + ), + 'restrict_content' => array( + 'label' => __( 'Restrict Content', 'convertkit' ), + 'type' => 'select', + 'description' => __( 'Select the Kit form, tag or product that the visitor must be subscribed to, permitting them access to view this member-only content.', 'convertkit' ), + 'values' => $restrict_content, + ), + ); + + } + + /** + * Returns this block's Default Values + * + * @since 3.3.0 + * + * @return array + */ + public function get_default_values() { + + return array( + 'form' => '-1', + 'landing_page' => '0', + 'tag' => '0', + 'restrict_content' => '0', + ); + + } + +} diff --git a/includes/plugin-sidebars/class-convertkit-plugin-sidebar.php b/includes/plugin-sidebars/class-convertkit-plugin-sidebar.php new file mode 100644 index 000000000..c3be8af11 --- /dev/null +++ b/includes/plugin-sidebars/class-convertkit-plugin-sidebar.php @@ -0,0 +1,161 @@ +get_name() ] = array( + 'name' => $this->get_name(), + 'minimum_capability' => $this->get_minimum_capability(), + 'meta_key' => $this->get_meta_key(), // phpcs:ignore WordPress.DB.SlowDBQuery.slow_db_query_meta_key + 'title' => $this->get_title(), + 'icon' => $this->get_icon(), + 'gutenberg_icon' => convertkit_get_file_contents( CONVERTKIT_PLUGIN_PATH . '/' . $this->get_icon() ), + 'fields' => $this->get_fields(), + 'attributes' => $this->get_attributes(), + 'default_values' => $this->get_default_values(), + ); + + return $plugin_sidebars; + + } + + /** + * Returns this plugin sidebar's meta key. + * + * @since 3.3.0 + */ + public function get_name() { + + return ''; + + } + + /** + * Returns this plugin sidebar's minimum capability required + * for displaying and permitting edits to the settings. + * + * @since 3.3.0 + * + * @return string + */ + public function get_minimum_capability() { + + return 'edit_posts'; + + } + + /** + * Returns this plugin sidebar's meta key. + * + * @since 3.3.0 + * + * @return string + */ + public function get_meta_key() { + + return ''; + + } + + /** + * Returns this plugin sidebar's title. + * + * @since 3.3.0 + */ + public function get_title() { + + return ''; + + } + + /** + * Returns this plugin sidebar's icon. + * + * @since 3.3.0 + */ + public function get_icon() { + + return ''; + + } + + /** + * Returns this plugin sidebar's attributes. + * + * @since 3.3.0 + * + * @return array + */ + public function get_attributes() { + + return array(); + + } + + /** + * Returns this plugin sidebar's Fields + * + * @since 3.3.0 + * + * @return array + */ + public function get_fields() { + + return array(); + + } + + /** + * Returns this plugin sidebar's Default Values + * + * @since 3.3.0 + * + * @return array + */ + public function get_default_values() { + + return array(); + + } + + /** + * Returns the given plugin sidebar's field's Default Value + * + * @since 3.3.0 + * + * @param string $field Field Name. + * @return string + */ + public function get_default_value( $field ) { + + $defaults = $this->get_default_values(); + if ( isset( $defaults[ $field ] ) ) { + return $defaults[ $field ]; + } + + return ''; + + } + +} diff --git a/resources/backend/images/kit-logo.svg b/resources/backend/images/kit-logo.svg index 946f6e5cc..dc2c1b69e 100644 --- a/resources/backend/images/kit-logo.svg +++ b/resources/backend/images/kit-logo.svg @@ -1,6 +1,6 @@ - + - + diff --git a/resources/backend/js/gutenberg.js b/resources/backend/js/gutenberg.js index ea849cf7f..a45df3db2 100644 --- a/resources/backend/js/gutenberg.js +++ b/resources/backend/js/gutenberg.js @@ -19,8 +19,17 @@ if (convertKitGutenbergEnabled()) { convertKitGutenbergRegisterBlock(convertkit_blocks[block]); } - // Register ConvertKit Pre-publish actions in Gutenberg if we're editing a Post. if (convertKitEditingPostInGutenberg()) { + // Register Plugin Sidebars in Gutenberg if we're editing a Post. + if (typeof convertkit_plugin_sidebars !== 'undefined') { + for (const pluginSidebar in convertkit_plugin_sidebars) { + convertKitGutenbergRegisterPluginSidebar( + convertkit_plugin_sidebars[pluginSidebar] + ); + } + } + + // Register ConvertKit Pre-publish actions in Gutenberg if we're editing a Post. if (typeof convertkit_pre_publish_actions !== 'undefined') { convertKitGutenbergRegisterPrePublishActions( convertkit_pre_publish_actions @@ -913,6 +922,173 @@ function convertKitGutenbergRegisterBlock(block) { ); } +/** + * Registers a Plugin Sidebar in Gutenberg. + * + * @since 3.3.0 + * + * @param {Object} sidebar Plugin Sidebars + */ +function convertKitGutenbergRegisterPluginSidebar(sidebar) { + (function (plugins, editor, element, components, data) { + // Define some constants for the various items we'll use. + const el = element.createElement; + const { registerPlugin } = plugins; + const { PluginSidebar } = editor; + const { TextControl, SelectControl, PanelBody, PanelRow } = components; + const { useSelect, useDispatch } = data; + + /** + * Returns a PluginDocumentSettingPanel for this plugin, containing + * post-level settings. + * + * @since 3.3.0 + */ + const RenderPanel = function () { + const meta = useSelect(function (wpSelect) { + return ( + wpSelect('core/editor').getEditedPostAttribute('meta') || {} + ); + }, []); + const { editPost: wpEditPost } = useDispatch('core/editor'); + const settings = meta[sidebar.meta_key] || sidebar.default_values; + + /** + * Updates the Post meta meta_key object. + * + * @since 3.3.0 + * + * @param {string} key Sub key within the meta_key object. + * @param {string} value Value to assign to the sub key. + */ + const updateSetting = function (key, value) { + wpEditPost({ + meta: { + [sidebar.meta_key]: Object.assign({}, settings, { + [key]: value, + }), + }, + }); + }; + + /** + * Return a field element for the settings panel. + * + * @since 3.3.0 + * + * @param {Object} field Field properties. + * @param {string} key Field name. + * @return {Object} Field element. + */ + const getField = function (field, key) { + // Define some field properties shared across all field types. + const fieldProperties = { + key: 'convertkit_plugin_sidebar_' + key, + label: field.label, + help: Array.isArray(field.description) + ? field.description.join('\n\n') + : field.description, + value: settings[key] || field.default_value || '', + + // Add __next40pxDefaultSize and __nextHasNoMarginBottom properties, + // preventing deprecation notices in the block editor and opt in to the new styles + // from 7.0. + __next40pxDefaultSize: true, + __nextHasNoMarginBottom: true, + + // Save Post Meta on value change. + onChange(value) { + updateSetting(key, value); + }, + }; + + const fieldOptions = []; + + // Define additional Field Properties and the Field Element, + // depending on the Field Type (select, textarea, text etc). + switch (field.type) { + case 'select': + // Build options for