Skip to content

Commit

Permalink
Merge pull request #167 from ConvertKit/opt-in-checkout-block
Browse files Browse the repository at this point in the history
Add Opt In Checkbox Block
  • Loading branch information
n7studios authored Dec 22, 2023
2 parents 643737e + be04928 commit a879101
Show file tree
Hide file tree
Showing 11 changed files with 1,559 additions and 45 deletions.
217 changes: 217 additions & 0 deletions includes/blocks/opt-in/class-ckwc-opt-in-block-integration.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,217 @@
<?php
/**
* ConvertKit Opt In Block class.
*
* @package CKWC
* @author ConvertKit
*/

use Automattic\WooCommerce\Blocks\Integrations\IntegrationInterface;

/**
* Registers an opt in checkbox block for the WooCommerce Checkout Block.
*
* @package CKWC
* @author ConvertKit
*/
class CKWC_Opt_In_Block_Integration implements IntegrationInterface {

/**
* Holds the WooCommerce Integration instance for this Plugin.
*
* @since 1.7.1
*
* @var CKWC_Integration
*/
private $integration;

/**
* The name of the integration.
*
* @since 1.7.1
*
* @return string
*/
public function get_name() {

return 'ckwc_opt_in';

}

/**
* The block metadata and attributes.
*
* @since 1.7.1
*
* @return array
*/
public function get_metadata() {

return array(
'name' => 'ckwc/opt-in',
'title' => __( 'ConvertKit Opt In', 'woocommerce-convertkit' ),
'category' => 'woocommerce',
'description' => __( 'Displays a ConvertKit opt in checkbox at Checkout.', 'woocommerce-convertkit' ),
'keywords' => array(
'subscriber',
'newsletter',
'email',
'convertkit',
'opt in',
'checkout',
),

// Don't support common block properties, and only permit this block once within the Checkout Block.
'supports' => array(
'html' => false,
'align' => false,
'multiple' => false,
'reusable' => false,

// Remove support for locking/unlocking this block in the block editor UI, as
// the block's visibility is controlled by the integration's settings.
'lock' => false,
),

// Where to display the block within the WooCommerce Checkout Block.
'parent' => array(
'woocommerce/checkout-contact-information-block',
),

// Attributes.
'attributes' => array(
// Lock the block so it cannot be deleted; the integration's settings will determine whether
// to display or hide the block.
'lock' => array(
'type' => 'object',
'default' => array(
'remove' => true,
'move' => true,
),
),

// The checkbox property.
'ckwc_opt_in' => array(
'type' => 'boolean',
),
),

// Editor script for this block.
'editor_script' => 'ckwc-opt-in-block',
);

}

/**
* Register frontend and backend scripts on block registration.
*
* @since 1.7.1
*/
public function initialize() {

// Fetch integration.
$this->integration = WP_CKWC_Integration();

$this->register_scripts();
$this->register_block();

}

/**
* Registers block editor and frontend checkout scripts.
*
* @since 1.7.1
*/
public function register_scripts() {

// Frontend checkout.
wp_register_script(
'ckwc-opt-in-block-frontend',
CKWC_PLUGIN_URL . 'resources/frontend/js/opt-in-block.js',
array(),
CKWC_PLUGIN_VERSION,
true
);

// Block editor.
wp_register_script(
'ckwc-opt-in-block',
CKWC_PLUGIN_URL . 'resources/backend/js/opt-in-block.js',
array(),
CKWC_PLUGIN_VERSION,
true
);

}

/**
* Registers the block with WordPress.
*
* @since 1.7.1
*/
public function register_block() {

// Get metadata.
$metadata = $this->get_metadata();

register_block_type(
$metadata['name'],
$metadata
);

}

/**
* Returns scripts to enqueue in the frontend site.
*
* @since 1.7.1
*
* @return array
*/
public function get_script_handles() {

return array( 'ckwc-opt-in-block-frontend' );

}

/**
* Returns scripts to enqueue in the block editor.
*
* @since 1.7.1
*
* @return array
*/
public function get_editor_script_handles() {

return array( 'ckwc-opt-in-block' );

}

/**
* Includes settings from CKWC_Integration as an object for the block editor
* and frontend scripts, which can be accessed using wc.wcSettings.getSetting( 'ckwc_opt_in_data' ),
* as these settings determine if the checkbox should be available, and if so its default checked
* state and label.
*
* Typically these would be presented as options to the user in the block
* editor, however this plugin has historically stored the settings
* at WooCommerce > Settings > Integrations > ConvertKit, prior to WooCommerce
* introducing the concept of a Checkout Block - so we need to honor those settings.
*
* @since 1.7.1
*/
public function get_script_data() {

return array(
'enabled' => $this->integration->is_enabled(),
'displayOptIn' => $this->integration->get_option_bool( 'display_opt_in' ),
'optInLabel' => $this->integration->get_option( 'opt_in_label' ),
'optInStatus' => $this->integration->get_option( 'opt_in_status' ),
'metadata' => $this->get_metadata(),
'integrationSettingsURL' => ckwc_get_settings_link(),
'integrationSettingsButtonLabel' => __( 'Configure Opt In', 'woocommerce-convertkit' ),
);

}

}
109 changes: 99 additions & 10 deletions includes/class-ckwc-checkout.php
Original file line number Diff line number Diff line change
Expand Up @@ -45,15 +45,72 @@ public function __construct() {
// If Display Opt In is enabled, show the Opt In checkbox at Checkout.
if ( $this->integration->get_option_bool( 'display_opt_in' ) ) {
add_filter( 'woocommerce_checkout_fields', array( $this, 'add_opt_in_checkbox' ) );
$this->register_opt_in_checkbox_store_api_endpoint();

}

// Store whether the customer should be opted in, in the Order's metadata.
// Store whether the customer should be opted in, in the Order's metadata, when using the Checkout shortcode.
add_action( 'woocommerce_checkout_update_order_meta', array( $this, 'save_opt_in_checkbox' ), 10, 1 );

// Store whether the customer should be opted in, in the Order's metadata, when using the Checkout block.
add_action( 'woocommerce_store_api_checkout_update_order_from_request', array( $this, 'save_opt_in_checkbox_block' ), 10, 2 );

}

/**
* When Display Opt In is enabled, register the `ckwc-opt-in` block as a Store API data endpoint.
*
* This allows the WooCommerce Checkout Block to pass the state of the opt in checkbox to
* woocommerce_store_api_checkout_update_order_from_request, which handles saving the opt in checkbox
* against the Order.
*
* @since 1.7.1
*/
public function register_opt_in_checkbox_store_api_endpoint() {

// Bail if the function isn't available.
if ( ! function_exists( 'woocommerce_store_api_register_endpoint_data' ) ) {
return;
}

woocommerce_store_api_register_endpoint_data(
array(
'endpoint' => \Automattic\WooCommerce\StoreApi\Schemas\V1\CheckoutSchema::IDENTIFIER,
'namespace' => 'ckwc-opt-in',
'schema_callback' => array( $this, 'schema' ),
'schema_type' => ARRAY_A,
)
);

}

/**
* Defines the Store API data schema for the opt in block.
*
* @since 1.7.1
*
* @return array
*/
public function schema() {

return array(
'ckwc_opt_in' => array(
'description' => 'foo',
'type' => 'boolean',
'context' => array( 'view', 'edit' ),
'optional' => true,
'arg_options' => array(
'validate_callback' => function ( $value ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
return true;
},
),
),
);

}

/**
* Adds the opt-in checkbox to the checkout's billing or order section, based
* Adds the opt-in checkbox to the WooCommerce Checkout shortcode's billing or order section, based
* on the Plugin's settings.
*
* @since 1.0.0
Expand Down Expand Up @@ -87,7 +144,7 @@ public function add_opt_in_checkbox( $fields ) {

/**
* Saves whether the customer should be subscribed to ConvertKit for this order
* when using the checkout.
* when using the Checkout shortcode.
*
* This function is not called if the 'Subscribe Customers' option is disabled
* in the Plugin settings.
Expand All @@ -98,25 +155,57 @@ public function add_opt_in_checkbox( $fields ) {
*/
public function save_opt_in_checkbox( $order_id ) {

$this->save_opt_in_for_order(
wc_get_order( $order_id ),
isset( $_POST['ckwc_opt_in'] ) // phpcs:ignore WordPress.Security.NonceVerification.Missing
);

}

/**
* Saves whether the customer should be subscribed to ConvertKit for this order
* when using the Checkout block.
*
* @since 1.7.1
*
* @param WC_Order $order WooCommerce Order.
* @param WP_REST_Request $request WordPress REST API request.
*/
public function save_opt_in_checkbox_block( $order, $request ) {

$this->save_opt_in_for_order(
$order,
(bool) ( array_key_exists( 'ckwc-opt-in', $request['extensions'] ) ? $request['extensions']['ckwc-opt-in']['ckwc_opt_in'] : false )
);

}

/**
* Save the opt in meta key/value pair against the given Order, based on the integration's
* settings and whether the checkbox was checked.
*
* @since 1.7.1
*
* @param WC_Order $order WooCommerce Order.
* @param bool $checkbox_checked Whether the opt in checkbox was checked.
*/
private function save_opt_in_for_order( $order, $checkbox_checked = false ) {

// Bail if the given Order ID isn't for a WooCommerce Order.
// Third party Plugins e.g. WooCommerce Subscriptions may call the `woocommerce_checkout_update_order_meta`
// action with a non-Order ID, resulting in inadvertent opt ins.
if ( OrderUtil::get_order_type( $order_id ) !== 'shop_order' ) {
if ( OrderUtil::get_order_type( $order->get_id() ) !== 'shop_order' ) {
return;
}

// Get order.
$order = wc_get_order( $order_id );

// Don't opt in by default.
$opt_in = 'no';

// If no opt in checkbox is displayed, opt in.
if ( ! $this->integration->get_option_bool( 'display_opt_in' ) ) {
$opt_in = 'yes';
} elseif ( isset( $_POST['ckwc_opt_in'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification
// Opt in checkbox is displayed at checkout.
// Opt in if it is checked.
} elseif ( $checkbox_checked ) {
// Opt in checkbox is displayed at checkout and was checked.
$opt_in = 'yes';
}

Expand Down
Loading

0 comments on commit a879101

Please sign in to comment.