initial
This commit is contained in:
@@ -0,0 +1,427 @@
|
||||
<?php
|
||||
/**
|
||||
* ActiveCampaign form action.
|
||||
*
|
||||
* Subscribes form submissions to an ActiveCampaign list.
|
||||
*
|
||||
* @package GenerateBlocksPro
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* ActiveCampaign form action class.
|
||||
*/
|
||||
class GenerateBlocks_Pro_Form_Action_ActiveCampaign {
|
||||
|
||||
/**
|
||||
* Register the provider with the registry.
|
||||
*/
|
||||
public static function init() {
|
||||
generateblocks_pro_register_form_integration(
|
||||
[
|
||||
'id' => 'activecampaign',
|
||||
'label' => __( 'ActiveCampaign', 'generateblocks-pro' ),
|
||||
'type' => 'email',
|
||||
'connection' => [
|
||||
'type' => 'api_key',
|
||||
'fields' => [
|
||||
[
|
||||
'key' => 'api_url',
|
||||
'label' => __( 'API URL', 'generateblocks-pro' ),
|
||||
'type' => 'url',
|
||||
'placeholder' => 'https://youraccount.api-us1.com',
|
||||
'help' => __( 'Paste the account-specific API URL from ActiveCampaign under Settings > Developer. It should look like https://youraccount.api-us1.com.', 'generateblocks-pro' ),
|
||||
'required' => true,
|
||||
'define' => 'GENERATEBLOCKS_PRO_ACTIVECAMPAIGN_API_URL',
|
||||
],
|
||||
[
|
||||
'key' => 'api_key',
|
||||
'label' => __( 'API Key', 'generateblocks-pro' ),
|
||||
'type' => 'password',
|
||||
'help' => __( 'Find your API key in ActiveCampaign under Settings > Developer.', 'generateblocks-pro' ),
|
||||
'required' => true,
|
||||
'define' => 'GENERATEBLOCKS_PRO_ACTIVECAMPAIGN_API_KEY',
|
||||
],
|
||||
],
|
||||
'test_callback' => function ( $settings ) {
|
||||
return self::test_connection(
|
||||
$settings['api_key'] ?? '',
|
||||
$settings['api_url'] ?? ''
|
||||
);
|
||||
},
|
||||
],
|
||||
'destination' => [
|
||||
'label' => __( 'List', 'generateblocks-pro' ),
|
||||
'empty_label' => __( '- Select -', 'generateblocks-pro' ),
|
||||
'refresh_label' => __( 'Refresh', 'generateblocks-pro' ),
|
||||
'required' => true,
|
||||
'options_callback' => function () {
|
||||
return self::fetch_lists( self::get_api_key(), self::get_api_url() );
|
||||
},
|
||||
],
|
||||
'field_map' => [
|
||||
'label' => __( 'Fields', 'generateblocks-pro' ),
|
||||
'empty_label' => __( 'Don\'t map', 'generateblocks-pro' ),
|
||||
'refresh_label' => __( 'Refresh fields', 'generateblocks-pro' ),
|
||||
'options_callback' => function () {
|
||||
return self::fetch_fields( self::get_api_key(), self::get_api_url() );
|
||||
},
|
||||
],
|
||||
'subscribe_callback' => [ __CLASS__, 'subscribe' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe using the generic email-signup settings shape.
|
||||
*
|
||||
* @param array $sanitized_data The sanitized form data.
|
||||
* @param array $settings Email signup settings.
|
||||
* @param array $context Request context.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function subscribe( $sanitized_data, $settings, $context = [] ) {
|
||||
unset( $context );
|
||||
|
||||
$api_key = self::get_api_key();
|
||||
$api_url = self::get_api_url();
|
||||
|
||||
if ( empty( $api_key ) || empty( $api_url ) ) {
|
||||
return new WP_Error( 'no_connection', 'ActiveCampaign API URL and key are not configured.' );
|
||||
}
|
||||
|
||||
$list_id = sanitize_text_field( $settings['destinationId'] ?? '' );
|
||||
|
||||
if ( empty( $list_id ) ) {
|
||||
return new WP_Error( 'no_list', 'ActiveCampaign list not selected.' );
|
||||
}
|
||||
|
||||
if ( ! preg_match( '/^\d{1,20}$/', $list_id ) ) {
|
||||
return new WP_Error( 'invalid_list', 'Invalid ActiveCampaign list ID.' );
|
||||
}
|
||||
|
||||
$email_field = $settings['emailField'] ?? 'email';
|
||||
$email = $sanitized_data[ $email_field ]['value'] ?? '';
|
||||
|
||||
if ( empty( $email ) || ! is_email( $email ) ) {
|
||||
return new WP_Error( 'invalid_email', 'Valid email required for ActiveCampaign.' );
|
||||
}
|
||||
|
||||
$contact = [
|
||||
'email' => $email,
|
||||
];
|
||||
|
||||
$field_values = [];
|
||||
$field_map = $settings['fieldMap'] ?? [];
|
||||
|
||||
foreach ( $field_map as $form_field => $activecampaign_field ) {
|
||||
$activecampaign_field = sanitize_text_field( $activecampaign_field );
|
||||
|
||||
if ( empty( $activecampaign_field ) || ! isset( $sanitized_data[ $form_field ]['value'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = GenerateBlocks_Pro_Form_Http::normalize_field_value( $sanitized_data[ $form_field ]['value'] );
|
||||
|
||||
if ( '' === $value ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( in_array( $activecampaign_field, [ 'firstName', 'lastName', 'phone' ], true ) ) {
|
||||
$contact[ $activecampaign_field ] = $value;
|
||||
} elseif ( preg_match( '/^\d{1,20}$/', $activecampaign_field ) ) {
|
||||
$field_values[] = [
|
||||
'field' => $activecampaign_field,
|
||||
'value' => $value,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $field_values ) ) {
|
||||
$contact['fieldValues'] = $field_values;
|
||||
}
|
||||
|
||||
$sync = self::request(
|
||||
'POST',
|
||||
'/contact/sync',
|
||||
$api_key,
|
||||
$api_url,
|
||||
[
|
||||
'contact' => $contact,
|
||||
]
|
||||
);
|
||||
|
||||
if ( is_wp_error( $sync ) ) {
|
||||
return $sync;
|
||||
}
|
||||
|
||||
$contact_id = $sync['contact']['id'] ?? 0;
|
||||
|
||||
if ( ! preg_match( '/^\d{1,20}$/', (string) $contact_id ) ) {
|
||||
return new WP_Error( 'activecampaign_error', 'ActiveCampaign: contact sync returned no contact ID.' );
|
||||
}
|
||||
|
||||
return self::ensure_list_membership( $api_key, $api_url, (string) $contact_id, $list_id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stored API key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_api_key() {
|
||||
return GenerateBlocks_Pro_Form_Integration_Rest::get_api_key_for( 'activecampaign' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stored API URL.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_api_url() {
|
||||
return GenerateBlocks_Pro_Form_Integration_Rest::get_connection_setting_for( 'activecampaign', 'api_url' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch lists from ActiveCampaign.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @param string $api_url The account API URL.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function fetch_lists( $api_key, $api_url ) {
|
||||
$response = self::request( 'GET', '/lists?limit=100', $api_key, $api_url );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ( empty( $response['lists'] ) || ! is_array( $response['lists'] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function ( $list ) {
|
||||
return [
|
||||
'id' => $list['id'] ?? '',
|
||||
'name' => $list['name'] ?? '',
|
||||
];
|
||||
},
|
||||
$response['lists']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch standard and custom contact fields from ActiveCampaign.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @param string $api_url The account API URL.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function fetch_fields( $api_key, $api_url ) {
|
||||
$options = [
|
||||
[
|
||||
'value' => 'firstName',
|
||||
'label' => __( 'First name', 'generateblocks-pro' ),
|
||||
],
|
||||
[
|
||||
'value' => 'lastName',
|
||||
'label' => __( 'Last name', 'generateblocks-pro' ),
|
||||
],
|
||||
[
|
||||
'value' => 'phone',
|
||||
'label' => __( 'Phone', 'generateblocks-pro' ),
|
||||
],
|
||||
];
|
||||
|
||||
$response = self::request( 'GET', '/fields?limit=100', $api_key, $api_url );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ( empty( $response['fields'] ) || ! is_array( $response['fields'] ) ) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
foreach ( $response['fields'] as $field ) {
|
||||
$id = $field['id'] ?? '';
|
||||
$title = $field['title'] ?? '';
|
||||
|
||||
if ( ! preg_match( '/^\d{1,20}$/', (string) $id ) || '' === $title ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$options[] = [
|
||||
'value' => (string) $id,
|
||||
'label' => $title,
|
||||
];
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the API connection.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @param string $api_url The account API URL.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function test_connection( $api_key, $api_url ) {
|
||||
$response = self::request( 'GET', '/users/me', $api_key, $api_url );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return new WP_Error( 'connection_failed', $response->get_error_message() );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe a contact to the selected list without re-subscribing opt-outs.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @param string $api_url The account API URL.
|
||||
* @param string $contact_id ActiveCampaign contact ID.
|
||||
* @param string $list_id ActiveCampaign list ID.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
private static function ensure_list_membership( $api_key, $api_url, $contact_id, $list_id ) {
|
||||
$memberships = self::request( 'GET', '/contacts/' . $contact_id . '/contactLists', $api_key, $api_url );
|
||||
|
||||
if ( is_wp_error( $memberships ) ) {
|
||||
return $memberships;
|
||||
}
|
||||
|
||||
foreach ( $memberships['contactLists'] ?? [] as $membership ) {
|
||||
if ( (string) ( $membership['list'] ?? '' ) !== $list_id ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ( '1' === (string) ( $membership['status'] ?? '' ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return new WP_Error( 'activecampaign_unsubscribed', 'ActiveCampaign: contact is not active on this list.' );
|
||||
}
|
||||
|
||||
$subscribe = self::request(
|
||||
'POST',
|
||||
'/contactLists',
|
||||
$api_key,
|
||||
$api_url,
|
||||
[
|
||||
'contactList' => [
|
||||
'list' => $list_id,
|
||||
'contact' => $contact_id,
|
||||
'status' => 1,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
if ( is_wp_error( $subscribe ) ) {
|
||||
return $subscribe;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared ActiveCampaign request helper.
|
||||
*
|
||||
* @param string $method HTTP method.
|
||||
* @param string $path API path.
|
||||
* @param string $api_key API key.
|
||||
* @param string $api_url Account API URL.
|
||||
* @param array|null $body Optional JSON body.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private static function request( $method, $path, $api_key, $api_url, $body = null ) {
|
||||
$base = self::normalize_api_url( $api_url );
|
||||
|
||||
if ( '' === $base || '' === trim( (string) $api_key ) ) {
|
||||
return new WP_Error( 'connection_missing', 'ActiveCampaign API URL and key are required.' );
|
||||
}
|
||||
|
||||
$args = [
|
||||
'method' => $method,
|
||||
'timeout' => GenerateBlocks_Pro_Form_Http::DEFAULT_TIMEOUT,
|
||||
'headers' => [
|
||||
'Api-Token' => $api_key,
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
];
|
||||
|
||||
if ( null !== $body ) {
|
||||
$args['body'] = wp_json_encode( $body );
|
||||
}
|
||||
|
||||
$response = wp_safe_remote_request( $base . '/' . ltrim( $path, '/' ), $args );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$error = GenerateBlocks_Pro_Form_Http::response_error( $response, 'ActiveCampaign' );
|
||||
|
||||
if ( $error ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$decoded = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
return is_array( $decoded ) ? $decoded : [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize an ActiveCampaign account URL.
|
||||
*
|
||||
* @param string $api_url Raw URL.
|
||||
* @return string Base API URL, including /api/3.
|
||||
*/
|
||||
private static function normalize_api_url( $api_url ) {
|
||||
$api_url = trim( (string) $api_url );
|
||||
|
||||
if ( '' === $api_url || ! preg_match( '#^https://#i', $api_url ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$api_url = rtrim( $api_url, "/ \t\n\r\0\x0B" );
|
||||
$parts = wp_parse_url( $api_url );
|
||||
|
||||
if (
|
||||
! is_array( $parts )
|
||||
|| empty( $parts['host'] )
|
||||
|| ! empty( $parts['user'] )
|
||||
|| ! empty( $parts['pass'] )
|
||||
|| ! empty( $parts['query'] )
|
||||
|| ! empty( $parts['fragment'] )
|
||||
) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$path = $parts['path'] ?? '';
|
||||
|
||||
if ( ! in_array( $path, [ '', '/', '/api/3', '/api/3/' ], true ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$base = 'https://' . $parts['host'];
|
||||
|
||||
if ( ! empty( $parts['port'] ) ) {
|
||||
$base .= ':' . (int) $parts['port'];
|
||||
}
|
||||
|
||||
if ( ! filter_var( $base, FILTER_VALIDATE_URL ) ) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return $base . '/api/3';
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
<?php
|
||||
/**
|
||||
* Brevo form action.
|
||||
*
|
||||
* Subscribes form submissions to a Brevo list.
|
||||
*
|
||||
* @package GenerateBlocksPro
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Brevo form action class.
|
||||
*/
|
||||
class GenerateBlocks_Pro_Form_Action_Brevo {
|
||||
|
||||
/**
|
||||
* API base URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const API_BASE = 'https://api.brevo.com/v3';
|
||||
|
||||
/**
|
||||
* Register the provider with the registry.
|
||||
*/
|
||||
public static function init() {
|
||||
generateblocks_pro_register_form_integration(
|
||||
[
|
||||
'id' => 'brevo',
|
||||
'label' => __( 'Brevo', 'generateblocks-pro' ),
|
||||
'type' => 'email',
|
||||
'connection' => [
|
||||
'type' => 'api_key',
|
||||
'fields' => [
|
||||
[
|
||||
'key' => 'api_key',
|
||||
'label' => __( 'API Key', 'generateblocks-pro' ),
|
||||
'type' => 'password',
|
||||
'help' => __( 'Find your API key in Brevo under SMTP & API.', 'generateblocks-pro' ),
|
||||
'required' => true,
|
||||
'define' => 'GENERATEBLOCKS_PRO_BREVO_API_KEY',
|
||||
],
|
||||
],
|
||||
'test_callback' => function ( $settings ) {
|
||||
return self::test_connection( $settings['api_key'] ?? '' );
|
||||
},
|
||||
],
|
||||
'destination' => [
|
||||
'label' => __( 'List', 'generateblocks-pro' ),
|
||||
'empty_label' => __( '- Select -', 'generateblocks-pro' ),
|
||||
'refresh_label' => __( 'Refresh', 'generateblocks-pro' ),
|
||||
'required' => true,
|
||||
'options_callback' => function () {
|
||||
return self::fetch_lists( self::get_api_key() );
|
||||
},
|
||||
],
|
||||
'field_map' => [
|
||||
'label' => __( 'Attributes', 'generateblocks-pro' ),
|
||||
'empty_label' => __( 'Don\'t map', 'generateblocks-pro' ),
|
||||
'refresh_label' => __( 'Refresh attributes', 'generateblocks-pro' ),
|
||||
'options_callback' => function () {
|
||||
return self::fetch_attributes( self::get_api_key() );
|
||||
},
|
||||
],
|
||||
'subscribe_callback' => [ __CLASS__, 'subscribe' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe using the generic email-signup settings shape.
|
||||
*
|
||||
* @param array $sanitized_data The sanitized form data.
|
||||
* @param array $settings Email signup settings.
|
||||
* @param array $context Request context.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function subscribe( $sanitized_data, $settings, $context = [] ) {
|
||||
unset( $context );
|
||||
|
||||
$api_key = self::get_api_key();
|
||||
|
||||
if ( empty( $api_key ) ) {
|
||||
return new WP_Error( 'no_api_key', 'Brevo API key not configured.' );
|
||||
}
|
||||
|
||||
$list_id_raw = sanitize_text_field( (string) ( $settings['destinationId'] ?? '' ) );
|
||||
|
||||
if ( '' === $list_id_raw ) {
|
||||
return new WP_Error( 'no_list', 'Brevo list not selected.' );
|
||||
}
|
||||
|
||||
if ( ! preg_match( '/^\d{1,18}$/', $list_id_raw ) ) {
|
||||
return new WP_Error( 'invalid_list', 'Invalid Brevo list ID.' );
|
||||
}
|
||||
|
||||
$list_id = (int) $list_id_raw;
|
||||
|
||||
$email_field = $settings['emailField'] ?? 'email';
|
||||
$email = $sanitized_data[ $email_field ]['value'] ?? '';
|
||||
|
||||
if ( empty( $email ) || ! is_email( $email ) ) {
|
||||
return new WP_Error( 'invalid_email', 'Valid email required for Brevo.' );
|
||||
}
|
||||
|
||||
$existing_contact = self::fetch_contact_by_email( $email, $api_key );
|
||||
|
||||
if ( is_wp_error( $existing_contact ) ) {
|
||||
$error_data = $existing_contact->get_error_data();
|
||||
$status = is_array( $error_data ) && isset( $error_data['status'] )
|
||||
? (int) $error_data['status']
|
||||
: 0;
|
||||
|
||||
if ( 404 !== $status ) {
|
||||
return $existing_contact;
|
||||
}
|
||||
} elseif ( self::contact_is_unsubscribed( $existing_contact, $list_id ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$attributes = [];
|
||||
$field_map = $settings['fieldMap'] ?? [];
|
||||
|
||||
foreach ( $field_map as $form_field => $attribute ) {
|
||||
$attribute = strtoupper( sanitize_text_field( $attribute ) );
|
||||
|
||||
if ( empty( $attribute ) || ! preg_match( '/^[A-Z0-9_]{1,64}$/', $attribute ) || ! isset( $sanitized_data[ $form_field ]['value'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = GenerateBlocks_Pro_Form_Http::normalize_field_value( $sanitized_data[ $form_field ]['value'] );
|
||||
|
||||
if ( '' !== $value ) {
|
||||
$attributes[ $attribute ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$body = [
|
||||
'email' => $email,
|
||||
'listIds' => [ $list_id ],
|
||||
'updateEnabled' => true,
|
||||
];
|
||||
|
||||
if ( ! empty( $attributes ) ) {
|
||||
$body['attributes'] = $attributes;
|
||||
}
|
||||
|
||||
$response = self::request( 'POST', '/contacts', $api_key, $body );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch an existing contact by email.
|
||||
*
|
||||
* @param string $email Contact email address.
|
||||
* @param string $api_key Brevo API key.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private static function fetch_contact_by_email( $email, $api_key ) {
|
||||
return self::request(
|
||||
'GET',
|
||||
'/contacts/' . rawurlencode( $email ) . '?identifierType=email_id',
|
||||
$api_key
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether a Brevo contact should not be re-subscribed.
|
||||
*
|
||||
* @param array $contact Contact data from Brevo.
|
||||
* @param int $list_id Destination list ID.
|
||||
* @return bool
|
||||
*/
|
||||
private static function contact_is_unsubscribed( $contact, $list_id ) {
|
||||
if ( ! is_array( $contact ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ( self::is_truthy( $contact['emailBlacklisted'] ?? false ) ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$list_unsubscribed = $contact['listUnsubscribed'] ?? [];
|
||||
|
||||
if ( ! is_array( $list_unsubscribed ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach ( $list_unsubscribed as $unsubscribed_list_id ) {
|
||||
if ( (int) $unsubscribed_list_id === $list_id ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpret common truthy values returned by APIs or stored test fixtures.
|
||||
*
|
||||
* @param mixed $value Value to test.
|
||||
* @return bool
|
||||
*/
|
||||
private static function is_truthy( $value ) {
|
||||
return true === $value || 1 === $value || '1' === $value || 'true' === $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stored API key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_api_key() {
|
||||
return GenerateBlocks_Pro_Form_Integration_Rest::get_api_key_for( 'brevo' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch contact lists from Brevo.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function fetch_lists( $api_key ) {
|
||||
$limit = 50;
|
||||
$offset = 0;
|
||||
$lists = [];
|
||||
$more = true;
|
||||
|
||||
while ( $more ) {
|
||||
$response = self::request(
|
||||
'GET',
|
||||
'/contacts/lists?limit=' . $limit . '&offset=' . $offset . '&sort=asc',
|
||||
$api_key
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ( empty( $response['lists'] ) || ! is_array( $response['lists'] ) ) {
|
||||
break;
|
||||
}
|
||||
|
||||
$lists = array_merge( $lists, $response['lists'] );
|
||||
$count = isset( $response['count'] ) ? (int) $response['count'] : count( $lists );
|
||||
$offset = $offset + $limit;
|
||||
$more = count( $lists ) < $count && $offset < 1000;
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function ( $list ) {
|
||||
return [
|
||||
'id' => $list['id'] ?? '',
|
||||
'name' => $list['name'] ?? '',
|
||||
'count' => $list['uniqueSubscribers'] ?? null,
|
||||
];
|
||||
},
|
||||
$lists
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch contact attributes from Brevo.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function fetch_attributes( $api_key ) {
|
||||
$response = self::request( 'GET', '/contacts/attributes', $api_key );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
if ( empty( $response['attributes'] ) || ! is_array( $response['attributes'] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$options = [];
|
||||
|
||||
foreach ( $response['attributes'] as $attribute ) {
|
||||
$name = strtoupper( sanitize_text_field( $attribute['name'] ?? '' ) );
|
||||
$category = sanitize_key( $attribute['category'] ?? '' );
|
||||
|
||||
if ( '' === $name || 'EMAIL' === $name || ! in_array( $category, [ 'normal', 'category', 'transactional' ], true ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$options[] = [
|
||||
'value' => $name,
|
||||
'label' => $name,
|
||||
];
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the API connection.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function test_connection( $api_key ) {
|
||||
$response = self::request( 'GET', '/account', $api_key );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return new WP_Error( 'connection_failed', $response->get_error_message() );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared Brevo request helper.
|
||||
*
|
||||
* @param string $method HTTP method.
|
||||
* @param string $path API path.
|
||||
* @param string $api_key API key.
|
||||
* @param array|null $body Optional JSON body.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
private static function request( $method, $path, $api_key, $body = null ) {
|
||||
if ( '' === trim( (string) $api_key ) ) {
|
||||
return new WP_Error( 'connection_missing', 'Brevo API key is required.' );
|
||||
}
|
||||
|
||||
$args = [
|
||||
'method' => $method,
|
||||
'timeout' => GenerateBlocks_Pro_Form_Http::DEFAULT_TIMEOUT,
|
||||
'headers' => [
|
||||
'api-key' => $api_key,
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
];
|
||||
|
||||
if ( null !== $body ) {
|
||||
$args['body'] = wp_json_encode( $body );
|
||||
}
|
||||
|
||||
$response = wp_safe_remote_request( self::API_BASE . $path, $args );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$error = GenerateBlocks_Pro_Form_Http::response_error( $response, 'Brevo' );
|
||||
|
||||
if ( $error ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$decoded = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
return is_array( $decoded ) ? $decoded : [];
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Built-in form integration loader.
|
||||
*
|
||||
* Loads the official Pro integration providers (Mailchimp, Kit, MailerLite,
|
||||
* ActiveCampaign, Brevo, Webhook) on the
|
||||
* `generateblocks_form_register_integrations` hook. Built-ins use the same
|
||||
* public registration entry point as third-party integrations, so the seam
|
||||
* between Pro and any future connectors plugin stays a directory move rather
|
||||
* than a code rewrite.
|
||||
*
|
||||
* @package GenerateBlocksPro
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
// Priority 5 so built-ins run first; third-party callbacks at default priority
|
||||
// 10 can then re-register the same provider ID to replace the built-in, which
|
||||
// is the documented override contract in class-form-integration-registry.php.
|
||||
add_action( 'generateblocks_form_register_integrations', 'generateblocks_pro_load_builtin_integrations', 5 );
|
||||
|
||||
/**
|
||||
* Require and initialize the built-in integration providers.
|
||||
*/
|
||||
function generateblocks_pro_load_builtin_integrations() {
|
||||
$dir = GENERATEBLOCKS_PRO_DIR . 'includes/form/integrations/';
|
||||
|
||||
require_once $dir . 'mailchimp.php';
|
||||
require_once $dir . 'convertkit.php';
|
||||
require_once $dir . 'mailerlite.php';
|
||||
require_once $dir . 'activecampaign.php';
|
||||
require_once $dir . 'brevo.php';
|
||||
require_once $dir . 'webhook.php';
|
||||
|
||||
GenerateBlocks_Pro_Form_Action_Mailchimp::init();
|
||||
GenerateBlocks_Pro_Form_Action_ConvertKit::init();
|
||||
GenerateBlocks_Pro_Form_Action_MailerLite::init();
|
||||
GenerateBlocks_Pro_Form_Action_ActiveCampaign::init();
|
||||
GenerateBlocks_Pro_Form_Action_Brevo::init();
|
||||
GenerateBlocks_Pro_Form_Integration_Webhook::init();
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
<?php
|
||||
/**
|
||||
* ConvertKit form action.
|
||||
*
|
||||
* Subscribes form submissions to a ConvertKit form or tag.
|
||||
*
|
||||
* @package GenerateBlocksPro
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* ConvertKit form action class.
|
||||
*/
|
||||
class GenerateBlocks_Pro_Form_Action_ConvertKit {
|
||||
|
||||
/**
|
||||
* API base URL for v4. Kit v4 API keys authenticate via the
|
||||
* X-Kit-Api-Key header; Bearer is reserved for OAuth tokens.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const API_BASE = 'https://api.kit.com/v4';
|
||||
|
||||
/**
|
||||
* Register the provider with the registry.
|
||||
*/
|
||||
public static function init() {
|
||||
generateblocks_pro_register_form_integration(
|
||||
[
|
||||
'id' => 'convertkit',
|
||||
'label' => __( 'Kit (ConvertKit)', 'generateblocks-pro' ),
|
||||
'type' => 'email',
|
||||
'connection' => [
|
||||
'type' => 'api_key',
|
||||
'fields' => [
|
||||
[
|
||||
'key' => 'api_key',
|
||||
'label' => __( 'API Key', 'generateblocks-pro' ),
|
||||
'type' => 'password',
|
||||
'help' => __( 'Find your v4 API key in Kit under Settings > Developer.', 'generateblocks-pro' ),
|
||||
'required' => true,
|
||||
'define' => 'GENERATEBLOCKS_PRO_CONVERTKIT_API_KEY',
|
||||
],
|
||||
],
|
||||
'test_callback' => function ( $settings ) {
|
||||
return self::test_connection( $settings['api_key'] ?? '' );
|
||||
},
|
||||
],
|
||||
'destination' => [
|
||||
'label' => __( 'Form', 'generateblocks-pro' ),
|
||||
'empty_label' => __( '- Select -', 'generateblocks-pro' ),
|
||||
'refresh_label' => __( 'Refresh', 'generateblocks-pro' ),
|
||||
'required' => true,
|
||||
'options_callback' => function () {
|
||||
return self::fetch_forms( self::get_api_key() );
|
||||
},
|
||||
],
|
||||
'field_map' => [
|
||||
'label' => __( 'Fields', 'generateblocks-pro' ),
|
||||
'empty_label' => __( 'Don\'t map', 'generateblocks-pro' ),
|
||||
'refresh_label' => __( 'Refresh fields', 'generateblocks-pro' ),
|
||||
'options_callback' => function () {
|
||||
return self::fetch_custom_fields( self::get_api_key() );
|
||||
},
|
||||
],
|
||||
'subscribe_callback' => [ __CLASS__, 'subscribe' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe using the generic email-signup settings shape.
|
||||
*
|
||||
* @param array $sanitized_data The sanitized form data.
|
||||
* @param array $settings Email signup settings.
|
||||
* @param array $context Request context.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function subscribe( $sanitized_data, $settings, $context = [] ) {
|
||||
$api_key = self::get_api_key();
|
||||
|
||||
if ( empty( $api_key ) ) {
|
||||
return new WP_Error( 'no_api_key', 'ConvertKit API key not configured.' );
|
||||
}
|
||||
|
||||
$form_id = sanitize_text_field( (string) ( $settings['destinationId'] ?? '' ) );
|
||||
|
||||
if ( '' === $form_id ) {
|
||||
return new WP_Error( 'no_form', 'ConvertKit form not selected.' );
|
||||
}
|
||||
|
||||
if ( ! preg_match( '/^\d{1,20}$/', $form_id ) ) {
|
||||
return new WP_Error( 'invalid_form', 'Invalid ConvertKit form ID.' );
|
||||
}
|
||||
|
||||
$email_field = $settings['emailField'] ?? 'email';
|
||||
$email = $sanitized_data[ $email_field ]['value'] ?? '';
|
||||
|
||||
if ( empty( $email ) || ! is_email( $email ) ) {
|
||||
return new WP_Error( 'invalid_email', 'Valid email required for ConvertKit.' );
|
||||
}
|
||||
|
||||
$body = [
|
||||
'email_address' => $email,
|
||||
];
|
||||
|
||||
// Map subscriber fields.
|
||||
$field_map = $settings['fieldMap'] ?? [];
|
||||
$fields = [];
|
||||
|
||||
foreach ( $field_map as $form_field => $ck_field ) {
|
||||
$ck_field = sanitize_text_field( $ck_field );
|
||||
|
||||
if ( empty( $ck_field ) || ! preg_match( '/^[A-Za-z0-9_]{1,100}$/', $ck_field ) || ! isset( $sanitized_data[ $form_field ]['value'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = GenerateBlocks_Pro_Form_Http::normalize_field_value( $sanitized_data[ $form_field ]['value'] );
|
||||
|
||||
if ( '' !== $value ) {
|
||||
if ( 'first_name' === $ck_field ) {
|
||||
$body['first_name'] = $value;
|
||||
} else {
|
||||
$fields[ $ck_field ] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $fields ) ) {
|
||||
$body['fields'] = $fields;
|
||||
}
|
||||
|
||||
// Kit v4 keeps subscriber profile updates separate from form association:
|
||||
// first create/update the subscriber, then add that subscriber to the
|
||||
// selected form with the required referrer payload.
|
||||
$create = self::request( 'POST', '/subscribers', $api_key, $body );
|
||||
|
||||
if ( is_wp_error( $create ) ) {
|
||||
return $create;
|
||||
}
|
||||
|
||||
$subscriber_id = $create['subscriber']['id'] ?? 0;
|
||||
|
||||
if ( ! is_scalar( $subscriber_id ) || ! preg_match( '/^\d{1,20}$/', (string) $subscriber_id ) ) {
|
||||
return new WP_Error( 'convertkit_error', 'ConvertKit: subscriber creation returned invalid id.' );
|
||||
}
|
||||
|
||||
$assoc = self::request(
|
||||
'POST',
|
||||
'/forms/' . $form_id . '/subscribers/' . (string) $subscriber_id,
|
||||
$api_key,
|
||||
[
|
||||
'referrer' => self::get_referrer( $context ),
|
||||
]
|
||||
);
|
||||
|
||||
if ( is_wp_error( $assoc ) ) {
|
||||
return $assoc;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a safe referrer value for Kit form association.
|
||||
*
|
||||
* @param array $context Request context.
|
||||
* @return string
|
||||
*/
|
||||
private static function get_referrer( $context ) {
|
||||
$page_url = isset( $context['page_url'] ) && is_scalar( $context['page_url'] )
|
||||
? trim( (string) $context['page_url'] )
|
||||
: '';
|
||||
|
||||
return filter_var( $page_url, FILTER_VALIDATE_URL ) ? $page_url : '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Shared Kit v4 request helper.
|
||||
*
|
||||
* Normalizes auth, error parsing, and logging across Kit endpoints.
|
||||
*
|
||||
* @param string $method HTTP method (GET/POST).
|
||||
* @param string $path Path beneath API_BASE (e.g. /subscribers).
|
||||
* @param string $key API key.
|
||||
* @param array|null $body Optional JSON body.
|
||||
* @return array|WP_Error Decoded JSON body on success, WP_Error on failure.
|
||||
*/
|
||||
private static function request( $method, $path, $key, $body = null ) {
|
||||
$endpoint = self::API_BASE . $path;
|
||||
|
||||
$args = [
|
||||
'method' => $method,
|
||||
'timeout' => GenerateBlocks_Pro_Form_Http::DEFAULT_TIMEOUT,
|
||||
'headers' => [
|
||||
'X-Kit-Api-Key' => $key,
|
||||
'Content-Type' => 'application/json',
|
||||
'Accept' => 'application/json',
|
||||
],
|
||||
];
|
||||
|
||||
if ( null !== $body ) {
|
||||
$args['body'] = wp_json_encode( $body );
|
||||
}
|
||||
|
||||
$response = wp_safe_remote_request( $endpoint, $args );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$status = wp_remote_retrieve_response_code( $response );
|
||||
$raw_body = wp_remote_retrieve_body( $response );
|
||||
$decoded = json_decode( $raw_body, true );
|
||||
|
||||
if ( $status >= 200 && $status < 300 ) {
|
||||
return is_array( $decoded ) ? $decoded : [];
|
||||
}
|
||||
|
||||
$detail = '';
|
||||
|
||||
if ( is_array( $decoded ) ) {
|
||||
if ( ! empty( $decoded['errors'] ) && is_array( $decoded['errors'] ) ) {
|
||||
$detail = implode( '; ', array_filter( array_map( 'strval', $decoded['errors'] ) ) );
|
||||
} elseif ( ! empty( $decoded['error'] ) && is_string( $decoded['error'] ) ) {
|
||||
$detail = $decoded['error'];
|
||||
} elseif ( ! empty( $decoded['message'] ) && is_string( $decoded['message'] ) ) {
|
||||
$detail = $decoded['message'];
|
||||
}
|
||||
}
|
||||
|
||||
if ( '' === $detail ) {
|
||||
$detail = 'HTTP ' . $status;
|
||||
}
|
||||
|
||||
if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
|
||||
// Log status + parsed error detail only. Raw body would echo the
|
||||
// submitted email back into the PHP error log on every auth/validation
|
||||
// failure.
|
||||
// phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log
|
||||
error_log( 'GenerateBlocks Form ConvertKit ' . $method . ' ' . $path . ' (status ' . $status . '): ' . sanitize_text_field( $detail ) );
|
||||
}
|
||||
|
||||
return new WP_Error( 'convertkit_error', 'ConvertKit: ' . sanitize_text_field( $detail ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stored API key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_api_key() {
|
||||
return GenerateBlocks_Pro_Form_Integration_Rest::get_api_key_for( 'convertkit' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch forms from ConvertKit.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function fetch_forms( $api_key ) {
|
||||
$body = self::request( 'GET', '/forms', $api_key );
|
||||
|
||||
if ( is_wp_error( $body ) ) {
|
||||
return $body;
|
||||
}
|
||||
|
||||
if ( empty( $body['forms'] ) || ! is_array( $body['forms'] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Drop Kit's auto-created system forms ("Creator Network", "Creator
|
||||
// Profile", and similar variants). They are surfaced by /v4/forms but
|
||||
// cannot be used as subscribe targets. Substring match on the name so
|
||||
// minor wording changes ("Creator Network Recommendations" vs just
|
||||
// "Creator Network") still catch.
|
||||
$blocked_name_fragments = [ 'creator network', 'creator profile' ];
|
||||
|
||||
$forms = array_filter(
|
||||
$body['forms'],
|
||||
function ( $form ) use ( $blocked_name_fragments ) {
|
||||
$name = strtolower( (string) ( $form['name'] ?? '' ) );
|
||||
|
||||
foreach ( $blocked_name_fragments as $fragment ) {
|
||||
if ( false !== strpos( $name, $fragment ) ) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
);
|
||||
|
||||
return array_values(
|
||||
array_map(
|
||||
function ( $form ) {
|
||||
return [
|
||||
'id' => $form['id'],
|
||||
'name' => $form['name'],
|
||||
];
|
||||
},
|
||||
$forms
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch custom fields from ConvertKit.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function fetch_custom_fields( $api_key ) {
|
||||
$response = self::request( 'GET', '/custom_fields?per_page=1000', $api_key );
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$options = [
|
||||
[
|
||||
'value' => 'first_name',
|
||||
'label' => __( 'First name (first_name)', 'generateblocks-pro' ),
|
||||
],
|
||||
];
|
||||
|
||||
if ( empty( $response['custom_fields'] ) || ! is_array( $response['custom_fields'] ) ) {
|
||||
return $options;
|
||||
}
|
||||
|
||||
foreach ( $response['custom_fields'] as $field ) {
|
||||
$key = sanitize_text_field( $field['key'] ?? '' );
|
||||
$label = sanitize_text_field( $field['label'] ?? $key );
|
||||
|
||||
if ( '' === $key || 'first_name' === $key || ! preg_match( '/^[A-Za-z0-9_]{1,100}$/', $key ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$options[] = [
|
||||
'value' => $key,
|
||||
'label' => $label . ' (' . $key . ')',
|
||||
];
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the API connection.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function test_connection( $api_key ) {
|
||||
$response = wp_safe_remote_get(
|
||||
self::API_BASE . '/account',
|
||||
[
|
||||
'timeout' => GenerateBlocks_Pro_Form_Http::DEFAULT_TIMEOUT,
|
||||
'headers' => [
|
||||
'X-Kit-Api-Key' => $api_key,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return new WP_Error( 'connection_failed', 'Could not connect to ConvertKit. Check your API key.' );
|
||||
}
|
||||
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
return new WP_Error( 'auth_failed', 'Invalid Kit API key. Kit v4 API keys are required — legacy v3 "API Secret" values will not authenticate.' );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,367 @@
|
||||
<?php
|
||||
/**
|
||||
* Mailchimp form action.
|
||||
*
|
||||
* Subscribes form submissions to a Mailchimp audience.
|
||||
*
|
||||
* @package GenerateBlocksPro
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mailchimp form action class.
|
||||
*/
|
||||
class GenerateBlocks_Pro_Form_Action_Mailchimp {
|
||||
|
||||
/**
|
||||
* Register the provider with the registry.
|
||||
*/
|
||||
public static function init() {
|
||||
generateblocks_pro_register_form_integration(
|
||||
[
|
||||
'id' => 'mailchimp',
|
||||
'label' => __( 'Mailchimp', 'generateblocks-pro' ),
|
||||
'type' => 'email',
|
||||
'connection' => [
|
||||
'type' => 'api_key',
|
||||
'fields' => [
|
||||
[
|
||||
'key' => 'api_key',
|
||||
'label' => __( 'API Key', 'generateblocks-pro' ),
|
||||
'type' => 'password',
|
||||
'help' => __( 'Find your API key in Mailchimp under Account > Extras > API Keys.', 'generateblocks-pro' ),
|
||||
'required' => true,
|
||||
'define' => 'GENERATEBLOCKS_PRO_MAILCHIMP_API_KEY',
|
||||
],
|
||||
],
|
||||
'test_callback' => function ( $settings ) {
|
||||
return self::test_connection( $settings['api_key'] ?? '' );
|
||||
},
|
||||
],
|
||||
'destination' => [
|
||||
'label' => __( 'Audience', 'generateblocks-pro' ),
|
||||
'empty_label' => __( '- Select -', 'generateblocks-pro' ),
|
||||
'refresh_label' => __( 'Refresh', 'generateblocks-pro' ),
|
||||
'required' => true,
|
||||
'options_callback' => function () {
|
||||
return self::fetch_audiences( self::get_api_key() );
|
||||
},
|
||||
],
|
||||
'field_map' => [
|
||||
'label' => __( 'Merge fields', 'generateblocks-pro' ),
|
||||
'empty_label' => __( 'Don\'t map', 'generateblocks-pro' ),
|
||||
'refresh_label' => __( 'Refresh fields', 'generateblocks-pro' ),
|
||||
'depends_on_destination' => true,
|
||||
'options_callback' => function ( $settings ) {
|
||||
$audience_id = sanitize_text_field( $settings['destinationId'] ?? '' );
|
||||
|
||||
if ( empty( $audience_id ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$fields = self::fetch_merge_fields( self::get_api_key(), $audience_id );
|
||||
|
||||
if ( is_wp_error( $fields ) ) {
|
||||
return $fields;
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function ( $field ) {
|
||||
$tag = $field['tag'] ?? '';
|
||||
|
||||
return [
|
||||
'value' => $tag,
|
||||
'label' => ( $field['name'] ?? $tag ) . ' (' . $tag . ')',
|
||||
];
|
||||
},
|
||||
$fields
|
||||
);
|
||||
},
|
||||
],
|
||||
'supports_double_optin' => true,
|
||||
'subscribe_callback' => [ __CLASS__, 'subscribe' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe using the generic email-signup settings shape.
|
||||
*
|
||||
* @param array $sanitized_data The sanitized form data.
|
||||
* @param array $settings Email signup settings.
|
||||
* @param array $context Request context.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function subscribe( $sanitized_data, $settings, $context = [] ) {
|
||||
unset( $context );
|
||||
|
||||
$api_key = self::get_api_key();
|
||||
|
||||
if ( empty( $api_key ) ) {
|
||||
return new WP_Error( 'no_api_key', 'Mailchimp API key not configured.' );
|
||||
}
|
||||
|
||||
$audience_id = sanitize_text_field( $settings['destinationId'] ?? '' );
|
||||
|
||||
if ( empty( $audience_id ) ) {
|
||||
return new WP_Error( 'no_audience', 'Mailchimp audience not selected.' );
|
||||
}
|
||||
|
||||
// Mailchimp audience IDs are 10-char hex strings. Strict shape match
|
||||
// catches admin typos before the API call rather than letting them
|
||||
// surface as opaque "404 not found" failures from Mailchimp.
|
||||
if ( ! preg_match( '/^[a-f0-9]{10}$/i', $audience_id ) ) {
|
||||
return new WP_Error( 'invalid_audience', 'Invalid Mailchimp audience ID.' );
|
||||
}
|
||||
|
||||
$email_field = $settings['emailField'] ?? 'email';
|
||||
$email = $sanitized_data[ $email_field ]['value'] ?? '';
|
||||
|
||||
if ( empty( $email ) || ! is_email( $email ) ) {
|
||||
return new WP_Error( 'invalid_email', 'Valid email required for Mailchimp.' );
|
||||
}
|
||||
|
||||
// Build merge fields from field mapping.
|
||||
$merge_fields = [];
|
||||
$field_map = $settings['fieldMap'] ?? [];
|
||||
|
||||
foreach ( $field_map as $form_field => $merge_tag ) {
|
||||
$merge_tag = sanitize_text_field( $merge_tag );
|
||||
|
||||
// Mailchimp merge tags are uppercase alphanumeric + underscores (e.g. FNAME).
|
||||
if ( ! empty( $merge_tag ) && preg_match( '/^[A-Z0-9_]{1,10}$/', $merge_tag ) && isset( $sanitized_data[ $form_field ]['value'] ) ) {
|
||||
$value = GenerateBlocks_Pro_Form_Http::normalize_field_value( $sanitized_data[ $form_field ]['value'] );
|
||||
|
||||
if ( '' !== $value ) {
|
||||
$merge_fields[ $merge_tag ] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$dc = self::get_data_center( $api_key );
|
||||
$subscriber_hash = md5( strtolower( $email ) );
|
||||
$endpoint = 'https://' . $dc . '.api.mailchimp.com/3.0/lists/' . $audience_id . '/members/' . $subscriber_hash;
|
||||
|
||||
$double_optin = $settings['doubleOptin'] ?? true;
|
||||
|
||||
// Mailchimp's PUT "Add or update list member" is idempotent:
|
||||
// - new subscriber → created with status = status_if_new
|
||||
// - existing member → status left alone, merge_fields updated
|
||||
// This matters because the previous POST endpoint silently dropped
|
||||
// merge-field changes for returning contacts (Mailchimp returned
|
||||
// "Member Exists" which we treated as success).
|
||||
//
|
||||
// We deliberately omit `status`; sending it would re-subscribe users
|
||||
// who had unsubscribed, which is both rude and a CAN-SPAM risk.
|
||||
$body = [
|
||||
'email_address' => $email,
|
||||
'status_if_new' => $double_optin ? 'pending' : 'subscribed',
|
||||
];
|
||||
|
||||
if ( ! empty( $merge_fields ) ) {
|
||||
$body['merge_fields'] = $merge_fields;
|
||||
}
|
||||
|
||||
$response = wp_safe_remote_request(
|
||||
$endpoint,
|
||||
[
|
||||
'method' => 'PUT',
|
||||
'timeout' => GenerateBlocks_Pro_Form_Http::DEFAULT_TIMEOUT,
|
||||
'headers' => [
|
||||
'Authorization' => 'Bearer ' . $api_key,
|
||||
'Content-Type' => 'application/json',
|
||||
],
|
||||
'body' => wp_json_encode( $body ),
|
||||
]
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$status_code = wp_remote_retrieve_response_code( $response );
|
||||
|
||||
// PUT returns 200 for both add and update. 400 with title "Member In
|
||||
// Compliance State" means the address previously unsubscribed — the
|
||||
// right behavior is to not silently re-subscribe them.
|
||||
if ( 200 !== $status_code ) {
|
||||
$response_body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
$error_detail = $response_body['detail'] ?? 'Unknown error';
|
||||
|
||||
return new WP_Error( 'mailchimp_error', 'Mailchimp: ' . sanitize_text_field( $error_detail ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stored API key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_api_key() {
|
||||
return GenerateBlocks_Pro_Form_Integration_Rest::get_api_key_for( 'mailchimp' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the data center from a Mailchimp API key.
|
||||
*
|
||||
* @param string $api_key The API key (format: key-dc).
|
||||
* @return string The data center identifier.
|
||||
*/
|
||||
private static function get_data_center( $api_key ) {
|
||||
// Mailchimp keys are formatted as "key-dc" where dc is after the last dash.
|
||||
$dash_pos = strrpos( $api_key, '-' );
|
||||
|
||||
if ( false === $dash_pos ) {
|
||||
return 'us1';
|
||||
}
|
||||
|
||||
$dc = substr( $api_key, $dash_pos + 1 );
|
||||
$dc = $dc ? $dc : 'us1';
|
||||
|
||||
// Validate data center format to prevent SSRF via crafted API keys.
|
||||
if ( ! preg_match( '/^[a-z]{2}\d+$/i', $dc ) ) {
|
||||
return 'us1';
|
||||
}
|
||||
|
||||
return $dc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch audiences (lists) from Mailchimp.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function fetch_audiences( $api_key ) {
|
||||
$dc = self::get_data_center( $api_key );
|
||||
$endpoint = 'https://' . $dc . '.api.mailchimp.com/3.0/lists?count=100&fields=lists.id,lists.name,lists.stats.member_count';
|
||||
|
||||
$response = wp_safe_remote_get(
|
||||
$endpoint,
|
||||
[
|
||||
'timeout' => GenerateBlocks_Pro_Form_Http::DEFAULT_TIMEOUT,
|
||||
'headers' => [
|
||||
'Authorization' => 'Bearer ' . $api_key,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$error = GenerateBlocks_Pro_Form_Http::response_error( $response, 'Mailchimp' );
|
||||
|
||||
if ( $error ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
if ( empty( $body['lists'] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function ( $list ) {
|
||||
return [
|
||||
'id' => $list['id'],
|
||||
'name' => $list['name'],
|
||||
'count' => $list['stats']['member_count'] ?? 0,
|
||||
];
|
||||
},
|
||||
$body['lists']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch merge fields for an audience.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @param string $audience_id The audience ID.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function fetch_merge_fields( $api_key, $audience_id ) {
|
||||
$audience_id = sanitize_text_field( $audience_id );
|
||||
|
||||
if ( ! preg_match( '/^[a-f0-9]{10}$/i', $audience_id ) ) {
|
||||
return new WP_Error( 'invalid_audience', 'Invalid audience ID.' );
|
||||
}
|
||||
|
||||
$dc = self::get_data_center( $api_key );
|
||||
$endpoint = 'https://' . $dc . '.api.mailchimp.com/3.0/lists/' . $audience_id . '/merge-fields?count=100';
|
||||
|
||||
$response = wp_safe_remote_get(
|
||||
$endpoint,
|
||||
[
|
||||
'timeout' => GenerateBlocks_Pro_Form_Http::DEFAULT_TIMEOUT,
|
||||
'headers' => [
|
||||
'Authorization' => 'Bearer ' . $api_key,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$error = GenerateBlocks_Pro_Form_Http::response_error( $response, 'Mailchimp' );
|
||||
|
||||
if ( $error ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
if ( empty( $body['merge_fields'] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function ( $field ) {
|
||||
return [
|
||||
'tag' => $field['tag'],
|
||||
'name' => $field['name'],
|
||||
'type' => $field['type'],
|
||||
];
|
||||
},
|
||||
$body['merge_fields']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the API connection.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function test_connection( $api_key ) {
|
||||
$dc = self::get_data_center( $api_key );
|
||||
$endpoint = 'https://' . $dc . '.api.mailchimp.com/3.0/ping';
|
||||
|
||||
$response = wp_safe_remote_get(
|
||||
$endpoint,
|
||||
[
|
||||
'timeout' => GenerateBlocks_Pro_Form_Http::DEFAULT_TIMEOUT,
|
||||
'headers' => [
|
||||
'Authorization' => 'Bearer ' . $api_key,
|
||||
],
|
||||
]
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return new WP_Error( 'connection_failed', 'Could not reach Mailchimp.' );
|
||||
}
|
||||
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
return new WP_Error( 'auth_failed', 'Invalid Mailchimp API key.' );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,308 @@
|
||||
<?php
|
||||
/**
|
||||
* MailerLite form action.
|
||||
*
|
||||
* Subscribes form submissions to a MailerLite group.
|
||||
*
|
||||
* @package GenerateBlocksPro
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* MailerLite form action class.
|
||||
*/
|
||||
class GenerateBlocks_Pro_Form_Action_MailerLite {
|
||||
|
||||
/**
|
||||
* API base URL.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
const API_BASE = 'https://connect.mailerlite.com/api';
|
||||
|
||||
/**
|
||||
* Shared request args for MailerLite.
|
||||
*
|
||||
* MailerLite's edge (Cloudflare) rejects WP's default `WordPress/X.Y; <url>`
|
||||
* User-Agent on some endpoints (notably /groups) with a 403 HTML page, while
|
||||
* others (/subscribers) pass. Set an explicit identifying UA so every call
|
||||
* gets through consistently.
|
||||
*
|
||||
* @param string $api_key API key.
|
||||
* @param array $extra_headers Additional headers to merge.
|
||||
* @return array
|
||||
*/
|
||||
private static function request_args( $api_key, $extra_headers = [] ) {
|
||||
return [
|
||||
'timeout' => GenerateBlocks_Pro_Form_Http::DEFAULT_TIMEOUT,
|
||||
'user-agent' => 'GenerateBlocks-Pro/' . ( defined( 'GENERATEBLOCKS_PRO_VERSION' ) ? GENERATEBLOCKS_PRO_VERSION : '0.0.0' ),
|
||||
'headers' => array_merge(
|
||||
[
|
||||
'Authorization' => 'Bearer ' . $api_key,
|
||||
'Accept' => 'application/json',
|
||||
],
|
||||
$extra_headers
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the provider with the registry.
|
||||
*/
|
||||
public static function init() {
|
||||
generateblocks_pro_register_form_integration(
|
||||
[
|
||||
'id' => 'mailerlite',
|
||||
'label' => __( 'MailerLite', 'generateblocks-pro' ),
|
||||
'type' => 'email',
|
||||
'connection' => [
|
||||
'type' => 'api_key',
|
||||
'fields' => [
|
||||
[
|
||||
'key' => 'api_key',
|
||||
'label' => __( 'API Key', 'generateblocks-pro' ),
|
||||
'type' => 'password',
|
||||
'help' => __( 'Find your API token in MailerLite under Integrations > API.', 'generateblocks-pro' ),
|
||||
'required' => true,
|
||||
'define' => 'GENERATEBLOCKS_PRO_MAILERLITE_API_KEY',
|
||||
],
|
||||
],
|
||||
'test_callback' => function ( $settings ) {
|
||||
return self::test_connection( $settings['api_key'] ?? '' );
|
||||
},
|
||||
],
|
||||
'destination' => [
|
||||
'label' => __( 'Group', 'generateblocks-pro' ),
|
||||
'empty_label' => __( '- Select -', 'generateblocks-pro' ),
|
||||
'refresh_label' => __( 'Refresh', 'generateblocks-pro' ),
|
||||
'required' => true,
|
||||
'options_callback' => function () {
|
||||
return self::fetch_groups( self::get_api_key() );
|
||||
},
|
||||
],
|
||||
'field_map' => [
|
||||
'label' => __( 'Fields', 'generateblocks-pro' ),
|
||||
'empty_label' => __( 'Don\'t map', 'generateblocks-pro' ),
|
||||
'refresh_label' => __( 'Refresh fields', 'generateblocks-pro' ),
|
||||
'options_callback' => function () {
|
||||
return self::fetch_fields( self::get_api_key() );
|
||||
},
|
||||
],
|
||||
'subscribe_callback' => [ __CLASS__, 'subscribe' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe using the generic email-signup settings shape.
|
||||
*
|
||||
* @param array $sanitized_data The sanitized form data.
|
||||
* @param array $settings Email signup settings.
|
||||
* @param array $context Request context.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function subscribe( $sanitized_data, $settings, $context = [] ) {
|
||||
unset( $context );
|
||||
|
||||
$api_key = self::get_api_key();
|
||||
|
||||
if ( empty( $api_key ) ) {
|
||||
return new WP_Error( 'no_api_key', 'MailerLite API key not configured.' );
|
||||
}
|
||||
|
||||
$group_id = sanitize_text_field( $settings['destinationId'] ?? '' );
|
||||
|
||||
if ( empty( $group_id ) ) {
|
||||
return new WP_Error( 'no_group', 'MailerLite group not selected.' );
|
||||
}
|
||||
|
||||
// MailerLite group IDs are numeric strings — validate to prevent path manipulation.
|
||||
if ( ! preg_match( '/^\d{1,20}$/', $group_id ) ) {
|
||||
return new WP_Error( 'invalid_group', 'Invalid MailerLite group ID.' );
|
||||
}
|
||||
|
||||
$email_field = $settings['emailField'] ?? 'email';
|
||||
$email = $sanitized_data[ $email_field ]['value'] ?? '';
|
||||
|
||||
if ( empty( $email ) || ! is_email( $email ) ) {
|
||||
return new WP_Error( 'invalid_email', 'Valid email required for MailerLite.' );
|
||||
}
|
||||
|
||||
$body = [
|
||||
'email' => $email,
|
||||
];
|
||||
|
||||
// Map name fields.
|
||||
$field_map = $settings['fieldMap'] ?? [];
|
||||
$fields = [];
|
||||
|
||||
foreach ( $field_map as $form_field => $ml_field ) {
|
||||
$ml_field = sanitize_text_field( $ml_field );
|
||||
|
||||
if ( empty( $ml_field ) || ! preg_match( '/^[A-Za-z0-9_]{1,64}$/', $ml_field ) || ! isset( $sanitized_data[ $form_field ]['value'] ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$value = GenerateBlocks_Pro_Form_Http::normalize_field_value( $sanitized_data[ $form_field ]['value'] );
|
||||
|
||||
if ( '' !== $value ) {
|
||||
$fields[ $ml_field ] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ( ! empty( $fields ) ) {
|
||||
$body['fields'] = $fields;
|
||||
}
|
||||
|
||||
$body['groups'] = [ $group_id ];
|
||||
|
||||
$response = wp_safe_remote_post(
|
||||
self::API_BASE . '/subscribers',
|
||||
array_merge(
|
||||
self::request_args( $api_key, [ 'Content-Type' => 'application/json' ] ),
|
||||
[ 'body' => wp_json_encode( $body ) ]
|
||||
)
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$status_code = wp_remote_retrieve_response_code( $response );
|
||||
|
||||
// 200 = updated existing, 201 = new subscriber.
|
||||
if ( ! in_array( $status_code, [ 200, 201 ], true ) ) {
|
||||
$response_body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
return new WP_Error(
|
||||
'mailerlite_error',
|
||||
'MailerLite: ' . sanitize_text_field( $response_body['message'] ?? 'Unknown error' )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the stored API key.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_api_key() {
|
||||
return GenerateBlocks_Pro_Form_Integration_Rest::get_api_key_for( 'mailerlite' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch groups from MailerLite.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function fetch_groups( $api_key ) {
|
||||
$response = wp_safe_remote_get(
|
||||
self::API_BASE . '/groups?limit=100&sort=name',
|
||||
self::request_args( $api_key )
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$error = GenerateBlocks_Pro_Form_Http::response_error( $response, 'MailerLite' );
|
||||
|
||||
if ( $error ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
if ( empty( $body['data'] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return array_map(
|
||||
function ( $group ) {
|
||||
return [
|
||||
'id' => $group['id'],
|
||||
'name' => $group['name'],
|
||||
'count' => $group['active_count'] ?? 0,
|
||||
];
|
||||
},
|
||||
$body['data']
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch fields from MailerLite.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function fetch_fields( $api_key ) {
|
||||
$response = wp_safe_remote_get(
|
||||
self::API_BASE . '/fields?limit=100&sort=name',
|
||||
self::request_args( $api_key )
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$error = GenerateBlocks_Pro_Form_Http::response_error( $response, 'MailerLite' );
|
||||
|
||||
if ( $error ) {
|
||||
return $error;
|
||||
}
|
||||
|
||||
$body = json_decode( wp_remote_retrieve_body( $response ), true );
|
||||
|
||||
if ( empty( $body['data'] ) || ! is_array( $body['data'] ) ) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$options = [];
|
||||
|
||||
foreach ( $body['data'] as $field ) {
|
||||
$key = sanitize_text_field( $field['key'] ?? '' );
|
||||
$name = sanitize_text_field( $field['name'] ?? $key );
|
||||
|
||||
if ( '' === $key || 'email' === $key || ! preg_match( '/^[A-Za-z0-9_]{1,64}$/', $key ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$options[] = [
|
||||
'value' => $key,
|
||||
'label' => $name . ' (' . $key . ')',
|
||||
];
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the API connection.
|
||||
*
|
||||
* @param string $api_key The API key.
|
||||
* @return true|WP_Error
|
||||
*/
|
||||
public static function test_connection( $api_key ) {
|
||||
$response = wp_safe_remote_get(
|
||||
self::API_BASE . '/subscribers?limit=0',
|
||||
self::request_args( $api_key )
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return new WP_Error( 'connection_failed', 'Could not connect to MailerLite. Check your API key.' );
|
||||
}
|
||||
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
return new WP_Error( 'auth_failed', 'Invalid MailerLite API key.' );
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Webhook email signup integration.
|
||||
*
|
||||
* Sends signup submissions to a configured webhook URL through the
|
||||
* email-signup action.
|
||||
*
|
||||
* @package GenerateBlocksPro
|
||||
*/
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Webhook email signup provider.
|
||||
*/
|
||||
class GenerateBlocks_Pro_Form_Integration_Webhook {
|
||||
|
||||
/**
|
||||
* Register the provider with the registry.
|
||||
*/
|
||||
public static function init() {
|
||||
generateblocks_pro_register_form_integration(
|
||||
[
|
||||
'id' => 'webhook',
|
||||
'label' => __( 'Webhook', 'generateblocks-pro' ),
|
||||
'type' => 'email',
|
||||
'settings_fields' => [
|
||||
[
|
||||
'key' => 'url',
|
||||
'label' => __( 'Webhook URL', 'generateblocks-pro' ),
|
||||
'type' => 'url',
|
||||
'placeholder' => 'https://example.com/webhook',
|
||||
'required' => true,
|
||||
],
|
||||
],
|
||||
'settings_validate_callback' => [ 'GenerateBlocks_Pro_Form_Action_Webhook', 'validate_signup_settings' ],
|
||||
'subscribe_callback' => [ 'GenerateBlocks_Pro_Form_Action_Webhook', 'subscribe' ],
|
||||
]
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user