From 9f3da56af876509d322e49741ceee3fb0432540a Mon Sep 17 00:00:00 2001
From: Christian Wach <needle@haystack.co.uk>
Date: Mon, 29 Nov 2021 15:18:05 +0000
Subject: [PATCH] Initial commit

---
 README.md                                     |  27 +-
 conditional-form-actions-for-acfe.php         | 241 ++++++
 cwps-acf-acfe-form.php                        | 608 +++++++++++++
 includes/cfafa-acfe.php                       | 131 +++
 .../form-actions/cfafa-form-action-base.php   | 498 +++++++++++
 .../form-actions/cfafa-form-action-email.php  | 815 ++++++++++++++++++
 .../cfafa-form-action-product.php             | 349 ++++++++
 .../cfafa-form-action-redirect.php            | 278 ++++++
 8 files changed, 2946 insertions(+), 1 deletion(-)
 create mode 100644 conditional-form-actions-for-acfe.php
 create mode 100644 cwps-acf-acfe-form.php
 create mode 100644 includes/cfafa-acfe.php
 create mode 100644 includes/form-actions/cfafa-form-action-base.php
 create mode 100644 includes/form-actions/cfafa-form-action-email.php
 create mode 100644 includes/form-actions/cfafa-form-action-product.php
 create mode 100644 includes/form-actions/cfafa-form-action-redirect.php

diff --git a/README.md b/README.md
index 7987d9d..c1b45f1 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,28 @@
 # Conditional Form Actions for ACFE
 
-Adds some Form Actions to ACFE that have a Conditional Field through which the Action can be bypassed.
\ No newline at end of file
+Adds some Form Actions to *ACF Extended* that have a Conditional Field through which the Action can be bypassed.
+
+## Basic Usage
+
+When creating an *ACF Extended* Form, you will see some new Form Actions:
+
+* _Conditional Redirect action_
+* _Conditional Email action_
+* _WooCommerce Product action_
+
+Add them to your *ACF Extended* Form to make use of the functionality that they offer.
+
+## Installation
+
+There are two ways to install from GitLab:
+
+### ZIP Download
+
+If you have downloaded *Conditional Form Actions for ACFE* as a ZIP file from the GitLab repository, do the following to install the plugin:
+
+1. Unzip the .zip file and, if needed, rename the enclosing folder so that the plugin's files are located directly inside `/wp-content/plugins/conditional-form-actions-for-acfe`
+2. *Conditional Form Actions for ACFE* is installed.
+
+### git clone
+
+If you have cloned the code from GitLab, it is assumed that you know what you're doing.
diff --git a/conditional-form-actions-for-acfe.php b/conditional-form-actions-for-acfe.php
new file mode 100644
index 0000000..737e669
--- /dev/null
+++ b/conditional-form-actions-for-acfe.php
@@ -0,0 +1,241 @@
+<?php /*
+--------------------------------------------------------------------------------
+Plugin Name: Conditional Form Actions for ACFE
+Plugin URI: https://develop.tadpole.cc/plugins/conditional-form-actions-for-acfe
+Description: Provides some ACF Extended Form Actions that have a Conditional Field.
+Author: Christian Wach
+Version: 0.1
+Author URI: https://haystack.co.uk
+Text Domain: conditional-form-actions-for-acfe
+Domain Path: /languages
+--------------------------------------------------------------------------------
+*/
+
+// Set plugin version here.
+define( 'CFAFA_VERSION', '0.1' );
+
+// Store reference to this file.
+if ( ! defined( 'CFAFA_FILE' ) ) {
+	define( 'CFAFA_FILE', __FILE__ );
+}
+
+// Store URL to this plugin's directory.
+if ( ! defined( 'CFAFA_URL' ) ) {
+	define( 'CFAFA_URL', plugin_dir_url( CFAFA_FILE ) );
+}
+
+// Store PATH to this plugin's directory.
+if ( ! defined( 'CFAFA_PATH' ) ) {
+	define( 'CFAFA_PATH', plugin_dir_path( CFAFA_FILE ) );
+}
+
+/**
+ * Conditional Form Actions for ACFE Class.
+ *
+ * A class that encapsulates this plugin's functionality.
+ *
+ * @since 0.1
+ */
+class Conditional_Form_Actions_For_ACFE {
+
+	/**
+	 * ACF Extended object.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var object $acfe The ACF Extended object.
+	 */
+	public $acfe;
+
+	/**
+	 * Initialises this object.
+	 *
+	 * @since 0.1
+	 */
+	public function __construct() {
+
+		// Always load translations.
+		add_action( 'plugins_loaded', [ $this, 'translation' ] );
+
+		// Initialise when ACF Extended is initialised.
+		add_action( 'acfe/init', [ $this, 'initialise' ] );
+
+	}
+
+	/**
+	 * Initialise this plugin.
+	 *
+	 * @since 0.1
+	 */
+	public function initialise() {
+
+		// Only do this once.
+		static $done;
+		if ( isset( $done ) && $done === true ) {
+			return;
+		}
+
+		// Include files.
+		$this->include_files();
+
+		// Set up objects and references.
+		$this->setup_objects();
+
+		/**
+		 * Broadcast that this plugin is loaded.
+		 *
+		 * @since 0.1
+		 */
+		do_action( 'cfafa/loaded' );
+
+		// We're done.
+		$done = true;
+
+	}
+
+	/**
+	 * Enable translation.
+	 *
+	 * @since 0.1
+	 */
+	public function translation() {
+
+		// Load translations.
+		load_plugin_textdomain(
+			'conditional-form-actions-for-acfe', // Unique name.
+			false, // Deprecated argument.
+			dirname( plugin_basename( CFAFA_FILE ) ) . '/languages/' // Relative path to files.
+		);
+
+	}
+
+	/**
+	 * Include files.
+	 *
+	 * @since 0.1
+	 */
+	public function include_files() {
+
+		// Load our class files.
+		require CFAFA_PATH . 'includes/cfafa-acfe.php';
+
+	}
+
+	/**
+	 * Set up this plugin's objects.
+	 *
+	 * @since 0.1
+	 */
+	public function setup_objects() {
+
+		// Initialise objects.
+		$this->acfe = new CFAFA_ACFE( $this );
+
+	}
+
+	/**
+	 * Check if this plugin is network activated.
+	 *
+	 * @since 0.1
+	 *
+	 * @return bool $is_network_active True if network activated, false otherwise.
+	 */
+	public function is_network_activated() {
+
+		// Only need to test once.
+		static $is_network_active;
+
+		// Have we done this already?
+		if ( isset( $is_network_active ) ) {
+			return $is_network_active;
+		}
+
+		// If not multisite, it cannot be.
+		if ( ! is_multisite() ) {
+			$is_network_active = false;
+			return $is_network_active;
+		}
+
+		// Make sure plugin file is included when outside admin.
+		if ( ! function_exists( 'is_plugin_active_for_network' ) ) {
+			require_once ABSPATH . '/wp-admin/includes/plugin.php';
+		}
+
+		// Get path from 'plugins' directory to this plugin.
+		$this_plugin = plugin_basename( CFAFA_FILE );
+
+		// Test if network active.
+		$is_network_active = is_plugin_active_for_network( $this_plugin );
+
+		// --<
+		return $is_network_active;
+
+	}
+
+} // Class ends.
+
+/**
+ * Load plugin if not yet loaded and return reference.
+ *
+ * @since 0.1
+ *
+ * @return Conditional_Form_Actions_For_ACFE $plugin The plugin reference.
+ */
+function cfafa() {
+
+	// Declare as global.
+	static $plugin;
+
+	// Instantiate plugin if not yet instantiated.
+	if ( ! isset( $plugin ) ) {
+		$plugin = new Conditional_Form_Actions_For_ACFE();
+	}
+
+	// --<
+	return $plugin;
+
+}
+
+// Load immediately.
+cfafa();
+
+/**
+ * Performs plugin activation tasks.
+ *
+ * @since 0.1
+ */
+function cfafa_activate() {
+
+	/**
+	 * Broadcast that this plugin has been activated.
+	 *
+	 * @since 0.1
+	 */
+	do_action( 'cfafa/activated' );
+
+}
+
+// Activation.
+register_activation_hook( __FILE__, 'cfafa_activate' );
+
+/**
+ * Performs plugin deactivation tasks.
+ *
+ * @since 0.1
+ */
+function cfafa_deactivated() {
+
+	/**
+	 * Broadcast that this plugin has been deactivated.
+	 *
+	 * @since 0.1
+	 */
+	do_action( 'cfafa/deactivated' );
+
+}
+
+// Deactivation.
+register_deactivation_hook( __FILE__, 'cfafa_deactivated' );
+
+// Uninstall uses the 'uninstall.php' method.
+// See: http://codex.wordpress.org/Function_Reference/register_uninstall_hook
diff --git a/cwps-acf-acfe-form.php b/cwps-acf-acfe-form.php
new file mode 100644
index 0000000..3a18ba3
--- /dev/null
+++ b/cwps-acf-acfe-form.php
@@ -0,0 +1,608 @@
+<?php
+/**
+ * ACFE Form Class.
+ *
+ * Handles compatibility with ACFE Forms.
+ *
+ * @package Conditional_Form_Actions_For_ACFE
+ * @since 0.1
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+
+
+/**
+ * CiviCRM Profile Sync ACFE Form Class.
+ *
+ * A class that handles compatibility with ACFE Forms.
+ *
+ * @since 0.1
+ */
+class CFAFA_Form {
+
+	/**
+	 * Plugin object.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var object $plugin The plugin object.
+	 */
+	public $plugin;
+
+	/**
+	 * ACF Loader object.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var object $acf_loader The ACF Loader object.
+	 */
+	public $acf_loader;
+
+	/**
+	 * CiviCRM object.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var object $civicrm The CiviCRM object.
+	 */
+	public $civicrm;
+
+	/**
+	 * ACF object.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var object $acf The ACF object.
+	 */
+	public $acf;
+
+	/**
+	 * Parent (calling) object.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var object $acfe The parent object.
+	 */
+	public $acfe;
+
+	/**
+	 * Supported Location Rule name.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var string $rule_name The supported Location Rule name.
+	 */
+	public $rule_name = 'form_civicrm';
+
+
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 0.1
+	 *
+	 * @param object $parent The parent object reference.
+	 */
+	public function __construct( $parent ) {
+
+		// Store references to objects.
+		$this->plugin = $parent->acf_loader->plugin;
+		$this->acf_loader = $parent->acf_loader;
+		$this->civicrm = $this->acf_loader->civicrm;
+		$this->acf = $this->acf_loader->acf;
+		$this->acfe = $parent;
+
+		// Init when this plugin is loaded.
+		add_action( 'cfafa/acf/acfe/loaded', [ $this, 'initialise' ] );
+
+	}
+
+
+
+	/**
+	 * Initialise this object.
+	 *
+	 * @since 0.1
+	 */
+	public function initialise() {
+
+		// Include files.
+		$this->include_files();
+
+		// Register Location Types.
+		$this->register_location_types();
+
+		// Register hooks.
+		$this->register_hooks();
+
+	}
+
+
+
+	/**
+	 * Include files.
+	 *
+	 * @since 0.1
+	 */
+	public function include_files() {
+
+	}
+
+
+
+	/**
+	 * Register WordPress hooks.
+	 *
+	 * @since 0.1
+	 */
+	public function register_hooks() {
+
+		// Listen for queries from the ACF Field Group class.
+		add_filter( 'cfafa/acf/query_field_group_mapped', [ $this, 'query_field_group_mapped' ], 10, 2 );
+		add_filter( 'cfafa/acf/field_group/query_supported_rules', [ $this, 'query_supported_rules' ], 10, 4 );
+
+		// Listen for queries from the ACF Field class.
+		add_filter( 'cfafa/acf/query_settings_field', [ $this, 'query_settings_field' ], 200, 3 );
+
+		// Register ACFE Form Actions.
+		add_filter( 'acfe/include_form_actions', [ $this, 'register_form_actions' ] );
+
+		// Add Form Actions Javascript.
+		add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_form_action_scripts' ] );
+
+		// Add Form Actions Javascript.
+		add_action( 'acfe/form/submit', [ $this, 'form_action_query_vars_clear' ] );
+
+		// Set a better Form Wrapper class.
+		add_filter( 'acfe/form/load', [ $this, 'form_wrapper' ], 10, 2 );
+
+	}
+
+
+
+	/**
+	 * Clear the Form Action Query Vars.
+	 *
+	 * This means we get a fresh set of Query Vars during the load process after
+	 * a Form has been submitted.
+	 *
+	 * @since 0.1
+	 */
+	public function form_action_query_vars_clear() {
+
+		// Clear the array of Action results.
+		set_query_var( 'acfe_form_actions', [] );
+
+	}
+
+
+
+	/**
+	 * Alters the default "Success Wrapper" class.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $form The ACF Form data array.
+	 * @param integer $post_id The numeric ID of the WordPress Post.
+	 * @return array $form The modified ACF Form data array.
+	 */
+	public function form_wrapper( $form, $post_id ) {
+
+		// Alter the default "Success Wrapper".
+		if ( $form['html_updated_message'] === '<div id="message" class="updated">%s</div>' ) {
+			$form['html_updated_message'] = '<div id="message" class="acfe-success">%s</div>';
+		}
+
+		// --<
+		return $form;
+
+	}
+
+
+
+	/**
+	 * Register Location Types.
+	 *
+	 * @since 0.1
+	 */
+	public function register_location_types() {
+
+		// Bail if less than ACF 5.9.0.
+		if ( ! function_exists( 'acf_register_location_type' ) ) {
+			return;
+		}
+
+		// Include Location Rule class files.
+		include CIVICRM_WP_PROFILE_SYNC_PATH . 'includes/acf/acfe/locations/cfafa-acf-acfe-location-bypass.php';
+
+		// Register Location Types with ACF.
+		acf_register_location_type( 'CiviCRM_Profile_Sync_ACF_Location_Type_Bypass' );
+
+	}
+
+
+
+	/**
+	 * Register Form Actions.
+	 *
+	 * @since 0.1
+	 */
+	public function register_form_actions() {
+
+		// Include Form Action base class.
+		include CIVICRM_WP_PROFILE_SYNC_PATH . 'includes/acf/acfe/form-actions/cfafa-acf-acfe-form-action-base.php';
+
+		// Include Form Action classes.
+		include CIVICRM_WP_PROFILE_SYNC_PATH . 'includes/acf/acfe/form-actions/cfafa-acf-acfe-form-action-contact.php';
+		include CIVICRM_WP_PROFILE_SYNC_PATH . 'includes/acf/acfe/form-actions/cfafa-acf-acfe-form-action-activity.php';
+		include CIVICRM_WP_PROFILE_SYNC_PATH . 'includes/acf/acfe/form-actions/cfafa-acf-acfe-form-action-participant.php';
+		include CIVICRM_WP_PROFILE_SYNC_PATH . 'includes/acf/acfe/form-actions/cfafa-acf-acfe-form-action-redirect.php';
+
+		// Init Form Actions.
+		new CFAFA_Form_Action_Contact( $this );
+		new CFAFA_Form_Action_Activity( $this );
+		new CFAFA_Form_Action_Participant( $this );
+		new CFAFA_Form_Action_Redirect( $this );
+
+		// Init Case Action if the CiviCase component is active.
+		$case_active = $this->plugin->civicrm->is_component_enabled( 'CiviCase' );
+		if ( $case_active ) {
+			include CIVICRM_WP_PROFILE_SYNC_PATH . 'includes/acf/acfe/form-actions/cfafa-acf-acfe-form-action-case.php';
+			new CFAFA_Form_Action_Case( $this );
+		}
+
+		// Init Email Action if the "Email API" Extension is active.
+		$email_active = $this->plugin->civicrm->is_extension_enabled( 'org.civicoop.emailapi' );
+		if ( $email_active ) {
+			include CIVICRM_WP_PROFILE_SYNC_PATH . 'includes/acf/acfe/form-actions/cfafa-acf-acfe-form-action-email.php';
+			new CFAFA_Form_Action_Email( $this );
+		}
+
+		if ( function_exists( 'WC' ) ) {
+			include CIVICRM_WP_PROFILE_SYNC_PATH . 'includes/acf/acfe/form-actions/cfafa-acf-acfe-form-action-product.php';
+			new CFAFA_Form_Action_Product( $this );
+		}
+
+	}
+
+
+
+	/**
+	 * Enqueue Form Action Javascript.
+	 *
+	 * @since 0.1
+	 */
+	public function enqueue_form_action_scripts() {
+
+		// Bail if the current screen is not an Edit ACFE Form screen.
+		$screen = get_current_screen();
+		if ( ! ( $screen instanceof WP_Screen ) ) {
+			return;
+		}
+		if ( $screen->base != 'post' || $screen->id != 'acfe-form' ) {
+			return;
+		}
+
+		// Add JavaScript plus dependencies.
+		wp_enqueue_script(
+			'cfafa-acfe-form-actions',
+			plugins_url( 'assets/js/acf/acfe/form-actions/cfafa-form-action-model.js', CIVICRM_WP_PROFILE_SYNC_FILE ),
+			[ 'acf-extended' ],
+			CIVICRM_WP_PROFILE_SYNC_VERSION // Version.
+		);
+
+		// Init the Contact Reference Field actions array.
+		$contact_action_refs = [
+			// Contact Actions must always be present.
+			'new_field/key=field_cfafa_contact_action_custom_alias' => 'newContactActionAlias',
+			'remove_field/key=field_cfafa_contact_action_custom_alias' => 'removeContactActionAlias',
+		];
+
+		/**
+		 * Query Form Action classes to build the Contact Reference Fields ACF Model actions array.
+		 *
+		 * @since 0.1
+		 *
+		 * @param array $contact_action_refs The ACF Model actions array to be populated.
+		 */
+		$contact_actions = apply_filters( 'cfafa/acf/acfe/form_actions/reference_fields/contact', $contact_action_refs );
+
+		// Init the Case Reference Field actions array.
+		$case_action_refs = [
+			// Case Actions must always be present.
+			'new_field/key=field_cfafa_case_action_custom_alias' => 'newCaseActionAlias',
+			'remove_field/key=field_cfafa_case_action_custom_alias' => 'removeCaseActionAlias',
+		];
+
+		/**
+		 * Query Form Action classes to build the Case Reference Fields ACF Model actions array.
+		 *
+		 * @since 0.1
+		 *
+		 * @param array $case_action_refs The ACF Model actions array to be populated.
+		 */
+		$case_actions = apply_filters( 'cfafa/acf/acfe/form_actions/reference_fields/case', $case_action_refs );
+
+		// Init the Participant Reference Field actions array.
+		$participant_action_refs = [
+			// Participant Actions must always be present.
+			'new_field/key=field_cfafa_participant_action_custom_alias' => 'newParticipantActionAlias',
+			'remove_field/key=field_cfafa_participant_action_custom_alias' => 'removeParticipantActionAlias',
+		];
+
+		/**
+		 * Query Form Action classes to build the Participant Reference Fields ACF Model actions array.
+		 *
+		 * @since 0.1
+		 *
+		 * @param array $participant_action_refs The ACF Model actions array to be populated.
+		 */
+		$participant_actions = apply_filters( 'cfafa/acf/acfe/form_actions/reference_fields/participant', $participant_action_refs );
+
+		// Build data array.
+		$vars = [
+			'localisation' => [],
+			'settings' => [
+				'contact_actions_reference' => $contact_actions,
+				'case_actions_reference' => $case_actions,
+				'participant_actions_reference' => $participant_actions,
+			],
+		];
+
+		// Localize our script.
+		wp_localize_script(
+			'cfafa-acfe-form-actions',
+			'CWPS_ACFE_Form_Action_Vars',
+			$vars
+		);
+
+	}
+
+
+
+	// -------------------------------------------------------------------------
+
+
+
+	/**
+	 * Listen for queries from the Field Group class.
+	 *
+	 * This method responds with a Boolean if it detects that this Field Group
+	 * should bypass ACF.
+	 *
+	 * @since 0.1
+	 *
+	 * @param bool $mapped The existing mapping flag.
+	 * @param array $field_group The array of ACF Field Group data.
+	 * @param bool $mapped True if the Field Group should bypass ACF, or pass through if not.
+	 */
+	public function query_field_group_mapped( $mapped, $field_group ) {
+
+		// Bail if a Mapping has already been found.
+		if ( $mapped !== false ) {
+			return $mapped;
+		}
+
+		// Bail if this is not a Bypass Field Group.
+		$is_bypass_field_group = $this->is_bypass_field_group( $field_group );
+		if ( $is_bypass_field_group === false ) {
+			return $mapped;
+		}
+
+		// --<
+		return true;
+
+	}
+
+
+
+	/**
+	 * Check if this Field Group should bypass ACF.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $field_group The Field Group to check.
+	 * @return array|bool The array of Entities if the Field Group should bypass ACF, or false otherwise.
+	 */
+	public function is_bypass_field_group( $field_group ) {
+
+		// Bail if there's no Field Group ID.
+		if ( empty( $field_group['ID'] ) ) {
+			return false;
+		}
+
+		// Only do this once per Field Group.
+		static $pseudocache;
+		if ( isset( $pseudocache[ $field_group['ID'] ] ) ) {
+			return $pseudocache[ $field_group['ID'] ];
+		}
+
+		// Assume not visible.
+		$is_visible = false;
+
+		// Bail if no Location Rules exist.
+		if ( ! empty( $field_group['location'] ) ) {
+
+			// We only need the key to test for an ACF Bypass location.
+			$params = [
+				$this->rule_name => 'foo',
+			];
+
+			// Do the check.
+			$is_visible = $this->acf_loader->acf->field_group->is_visible( $field_group, $params );
+
+		}
+
+		// Maybe add to pseudo-cache.
+		if ( ! isset( $pseudocache[ $field_group['ID'] ] ) ) {
+			$pseudocache[ $field_group['ID'] ] = $is_visible;
+		}
+
+		// --<
+		return $is_visible;
+
+	}
+
+
+
+	/**
+	 * Listen for queries for supported Location Rules.
+	 *
+	 * @since 0.1
+	 *
+	 * @param bool $supported The existing supported Location Rules status.
+	 * @param array $rule The Location Rule.
+	 * @param array $params The query params array.
+	 * @param array $field_group The ACF Field Group data array.
+	 * @return bool $supported The modified supported Location Rules status.
+	 */
+	public function query_supported_rules( $supported, $rule, $params, $field_group ) {
+
+		// Bail if already supported.
+		if ( $supported === true ) {
+			return $supported;
+		}
+
+		// Test for this Location Rule.
+		if ( $rule['param'] == $this->rule_name && ! empty( $params[$this->rule_name] ) ) {
+			$supported = true;
+		}
+
+		// --<
+		return $supported;
+
+	}
+
+
+
+	// -------------------------------------------------------------------------
+
+
+
+	/**
+	 * Returns a Setting Field from this Entity when found.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $setting_field The existing Setting Field array.
+	 * @param array $field The ACF Field data array.
+	 * @param array $field_group The ACF Field Group data array.
+	 * @return array|bool $setting_field The Setting Field array if populated, false if conflicting.
+	 */
+	public function query_settings_field( $setting_field, $field, $field_group ) {
+
+		// Pass if conflicting Fields have been found.
+		if ( $setting_field === false ) {
+			return false;
+		}
+
+		// Pass if this is not a Bypass Field Group.
+		$is_visible = $this->is_bypass_field_group( $field_group );
+		if ( $is_visible === false ) {
+			return $setting_field;
+		}
+
+		// If already populated, then this is a conflicting Field.
+		if ( ! empty( $setting_field ) ) {
+			return false;
+		}
+
+		// Get the array of Entities and IDs.
+		$entity_array = $this->entity_mapping_extract( $field_group['location'] );
+
+		/**
+		 * Request an array of Setting Field choices from Entity classes.
+		 *
+		 * @since 0.1
+		 *
+		 * @param array The empty default Setting Field choices array.
+		 * @param array $field The ACF Field data array.
+		 * @param array $field_group The ACF Field Group data array.
+		 * @param array $entity_array The array of Entities and IDs.
+		 */
+		$choices = apply_filters( 'cfafa/acf/bypass/query_settings_choices', [], $field, $field_group, $entity_array );
+
+		// Bail if there aren't any.
+		if ( empty( $choices ) ) {
+			return false;
+		}
+
+		// Define Setting Field.
+		$setting_field = [
+			'key' => $this->civicrm->acf_field_key_get(),
+			'label' => __( 'CiviCRM Field', 'conditional-form-actions-for-acfe' ),
+			'name' => $this->civicrm->acf_field_key_get(),
+			'type' => 'select',
+			'instructions' => __( 'Choose the CiviCRM Field that this ACF Field should sync with. (Optional)', 'conditional-form-actions-for-acfe' ),
+			'default_value' => '',
+			'placeholder' => '',
+			'allow_null' => 1,
+			'multiple' => 0,
+			'ui' => 0,
+			'required' => 0,
+			'return_format' => 'value',
+			'parent' => $this->acf->field_group->placeholder_group_get(),
+			'choices' => $choices,
+		];
+
+		// Return populated array.
+		return $setting_field;
+
+	}
+
+
+
+	/**
+	 * Returns an array containing the Entities and their IDs.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $location_rules The Location Rules for the Field.
+	 * @return array $entity_mapping The array containing the Entities and IDs.
+	 */
+	public function entity_mapping_extract( $location_rules ) {
+
+		// Init an empty Entity mapping array.
+		$entity_mapping = [];
+
+		// The Location Rules outer array is made of "grouos".
+		foreach ( $location_rules as $group ) {
+
+			// Skip group if it has no rules.
+			if ( empty( $group ) ) {
+				continue;
+			}
+
+			// The Location Rules inner array is made of "rules".
+			foreach ( $group as $rule ) {
+
+				// Is this a Bypass rule?
+				if ( $rule['param'] === $this->rule_name ) {
+
+					// Extract the Entity and ID.
+					//$entity_map = [];
+					$tmp = explode( '-', $rule['value'] );
+					//$entity_map[] = [ 'entity' => $tmp[0], 'entity_id' => (int) $tmp[1] ];
+
+					// Add to return.
+					$entity_mapping[ $tmp[0] ][] = (int) $tmp[1];
+
+				}
+
+			}
+
+		}
+
+		// --<
+		return $entity_mapping;
+
+	}
+
+
+
+} // Class ends.
+
+
+
diff --git a/includes/cfafa-acfe.php b/includes/cfafa-acfe.php
new file mode 100644
index 0000000..569fc3d
--- /dev/null
+++ b/includes/cfafa-acfe.php
@@ -0,0 +1,131 @@
+<?php
+/**
+ * ACF Extended Class.
+ *
+ * Handles general "ACF Extended" functionality.
+ *
+ * @package Conditional_Form_Actions_For_ACFE
+ * @since 0.1
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * "ACF Extended" Class.
+ *
+ * A class that encapsulates ACF Extended functionality.
+ *
+ * @since 0.1
+ */
+class CFAFA_ACFE {
+
+	/**
+	 * Plugin object.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var object $plugin The plugin object.
+	 */
+	public $plugin;
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 0.1
+	 *
+	 * @param object $plugin The plugin object.
+	 */
+	public function __construct( $plugin ) {
+
+		// Store references to objects.
+		$this->plugin = $plugin;
+
+		// Init when ACF class is loaded.
+		add_action( 'cfafa/loaded', [ $this, 'initialise' ] );
+
+	}
+
+	/**
+	 * Initialise this object.
+	 *
+	 * @since 0.1
+	 */
+	public function initialise() {
+
+		// Only do this once.
+		static $done;
+		if ( isset( $done ) && $done === true ) {
+			return;
+		}
+
+		// Register hooks.
+		$this->register_hooks();
+
+		/**
+		 * Broadcast that this class is now loaded.
+		 *
+		 * @since 0.1
+		 */
+		do_action( 'cfafa/acfe/loaded' );
+
+		// We're done and loaded.
+		$done = true;
+
+	}
+
+	/**
+	 * Register hooks.
+	 *
+	 * @since 0.1
+	 */
+	public function register_hooks() {
+
+		// Register ACFE Form Actions.
+		add_filter( 'acfe/include_form_actions', [ $this, 'register_form_actions' ], 50 );
+
+		// Add Form Actions Javascript.
+		add_action( 'acfe/form/submit', [ $this, 'form_action_query_vars_clear' ] );
+
+	}
+
+	/**
+	 * Clear the Form Action Query Vars.
+	 *
+	 * This means we get a fresh set of Query Vars during the load process after
+	 * a Form has been submitted.
+	 *
+	 * @since 0.1
+	 */
+	public function form_action_query_vars_clear() {
+
+		// Clear the array of Action results.
+		set_query_var( 'acfe_form_actions', [] );
+
+	}
+
+	/**
+	 * Register Form Actions.
+	 *
+	 * @since 0.1
+	 */
+	public function register_form_actions() {
+
+		// Include class files.
+		include CFAFA_PATH . 'includes/form-actions/cfafa-form-action-base.php';
+		include CFAFA_PATH . 'includes/form-actions/cfafa-form-action-redirect.php';
+		//include CFAFA_PATH . 'includes/form-actions/cfafa-form-action-email.php';
+
+		// Instantiate the Form Actions.
+		new CFAFA_Form_Action_Redirect( $this );
+		//new CFAFA_Form_Action_Email( $this );
+
+		// Maybe add WooCommerce Product Action.
+		if ( function_exists( 'WC' ) ) {
+			include CFAFA_PATH . 'includes/form-actions/cfafa-form-action-product.php';
+			new CFAFA_Form_Action_Product( $this );
+		}
+
+	}
+
+} // Class ends.
diff --git a/includes/form-actions/cfafa-form-action-base.php b/includes/form-actions/cfafa-form-action-base.php
new file mode 100644
index 0000000..095c74c
--- /dev/null
+++ b/includes/form-actions/cfafa-form-action-base.php
@@ -0,0 +1,498 @@
+<?php
+/**
+ * ACFE Base Form Action Class.
+ *
+ * Holds methods common to ACFE Form Action classes.
+ *
+ * @package Conditional_Form_Actions_For_ACFE
+ * @since 0.1
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * CiviCRM Profile Sync "Base" ACFE Form Action Class.
+ *
+ * A class that is extended by CiviCRM Profile Sync ACFE Form Action classes.
+ * *
+ * @since 0.1
+ */
+class CFAFA_Form_Action_Base {
+
+	/**
+	 * Form Action Name.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var string $action_name The unique name of the Form Action.
+	 */
+	public $action_name = '';
+
+	/**
+	 * Form Action Label.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var string $action_label The label of the Form Action.
+	 */
+	public $action_label = '';
+
+	/**
+	 * Form Action Alias Placeholder.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var string $alias_placeholder The alias placeholder for the Form Action.
+	 */
+	public $alias_placeholder = '';
+
+	/**
+	 * Field Key Prefix.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var string $field_key The prefix for the Field Key.
+	 */
+	public $field_key = '';
+
+	/**
+	 * Field Name Prefix.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var string $field_name The prefix for the Field Name.
+	 */
+	public $field_name = '';
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 0.1
+	 */
+	public function __construct() {
+
+		// Callback for the "acfe/form/load/..." hook.
+        add_filter( 'acfe/form/load/' . $this->action_name, [ $this, 'load' ], 10, 3 );
+
+		// Callback for the "acfe/form/make/..." hook.
+		add_action( 'acfe/form/make/' . $this->action_name, [ $this, 'make' ], 10, 3 );
+
+		// Generic callback for ACFE Form Actions hook.
+		add_filter( 'acfe/form/actions', [ $this, 'action_add' ] );
+
+	}
+
+	/**
+	 * Allow classes to configure themselves prior to the Layout being returned.
+	 *
+	 * @since 0.1
+	 */
+	public function configure() {}
+
+	/**
+	 * Performs tasks when the Form that the Action is attached to is loaded.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $form The array of Form data.
+	 * @param integer $current_post_id The ID of the Post in which the Form has been embedded.
+	 * @param string $action The customised name of the action.
+	 */
+	public function load( $form, $current_post_id, $action ) {
+		return $form;
+	}
+
+	/**
+	 * Saves the result of the Action for use by subsequent Actions.
+	 *
+	 * @since 0.1
+	 *
+	 * @param string $action The name of the Action.
+	 * @param array $data The result of the Action.
+	 */
+	public function load_action_save( $action = '', $data ) {
+
+		// Get the existing array of Action results.
+		$actions = get_query_var( 'acfe_form_actions', [] );
+
+		$actions[$this->action_name] = $data;
+		if ( ! empty( $action ) ) {
+			$actions[$action] = $data;
+		}
+
+		// Update array of Action results.
+		set_query_var( 'acfe_form_actions', $actions );
+
+	}
+
+	/**
+	 * Performs the Action when the Form the Action is attached to is submitted.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $form The array of Form data.
+	 * @param integer $current_post_id The ID of the Post from which the Form has been submitted.
+	 * @param string $action The customised name of the action.
+	 */
+	public function make( $form, $current_post_id, $action ) {}
+
+	/**
+	 * Maybe skip the Action when the Form the Action is attached to is submitted.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $form The array of Form data.
+	 * @param integer $current_post_id The ID of the Post from which the Form has been submitted.
+	 * @param string $action The customised name of the Form Action.
+	 * @return bool $prepare The net result of the set of filters.
+	 */
+	public function make_skip( $form, $current_post_id, $action ) {
+
+		// Get some Form details.
+		$form_name = acf_maybe_get( $form, 'name' );
+		$form_id = acf_maybe_get( $form, 'ID' );
+
+		// Assume we're good to go.
+		$prepare = true;
+
+		/**
+		 * Allow others to prevent Form Action.
+		 *
+		 * Returning false for any of these filters will skip the Action.
+		 *
+		 * @since 0.1
+		 *
+		 * @param bool $prepare True by default so that the Form Action goes ahead.
+		 * @param array $form The array of Form data.
+		 * @param integer $current_post_id The ID of the Post from which the Form has been submitted.
+		 * @param string $action The customised name of the Form Action.
+		 */
+		$filter = 'acfe/form/prepare/' . $this->action_name;
+		$prepare = apply_filters( $filter, $prepare, $form, $current_post_id, $action );
+		$prepare = apply_filters( $filter . '/form=' . $form_name, $prepare, $form, $current_post_id, $action );
+		if ( ! empty( $action ) ) {
+			$prepare = apply_filters( $filter . '/action=' . $action, $prepare, $form, $current_post_id, $action );
+		}
+
+		// --<
+		return $prepare;
+
+	}
+
+	/**
+	 * Saves the result of the Action for use by subsequent Actions.
+	 *
+	 * @since 0.1
+	 *
+	 * @param string $action The name of the Action.
+	 * @param array $data The result of the Action.
+	 */
+	public function make_action_save( $action = '', $data ) {
+
+		// Get the existing array of Action results.
+		$actions = get_query_var( 'acfe_form_actions', [] );
+
+		$actions[$this->action_name] = $data;
+		if ( ! empty( $action ) ) {
+			$actions[$action] = $data;
+		}
+
+		// Update array of Action results.
+		set_query_var( 'acfe_form_actions', $actions );
+
+	}
+
+	/**
+	 * Defines the action by adding a layout.
+	 *
+	 * The "name" value of the layout determines the construction of the
+	 * "acfe/form/load/..." and "acfe/form/make/..." actions.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $layouts The existing layouts.
+	 * @return array $layouts The modified layouts.
+	 */
+	public function action_add( $layouts ) {
+
+		// Let the classes that extend this one configure themselves.
+		$this->configure();
+
+		// Init our layout.
+		$layout = [
+			'key' => 'layout_' . $this->action_name,
+			'name' => $this->action_name,
+			'label' => $this->action_label,
+			'display' => 'row',
+			'min' => '',
+			'max' => '',
+		];
+
+		// Build Action Tab.
+		$action_tab_fields = $this->tab_action_add();
+
+		// Build Mapping Tab.
+		$mapping_tab_fields = $this->tab_mapping_add();
+
+		// Combine Sub-Fields.
+		$sub_fields = array_merge(
+			$action_tab_fields,
+			$mapping_tab_fields
+		);
+
+		/**
+		 * Let the classes that extend this one modify the Sub-Fields.
+		 *
+		 * @since 0.1
+		 *
+		 * @param array $sub_fields The array of Sub-Fields.
+		 */
+		$layout['sub_fields'] = apply_filters( 'cfafa/acfe/form/actions/sub_fields', $sub_fields );
+
+		// Add our completed layout to the layouts array.
+		$layouts['layout_' . $this->action_name] = $layout;
+
+		// --<
+		return $layouts;
+
+	}
+
+	/**
+	 * Defines the "Action" Tab.
+	 *
+	 * These Fields are required to configure the Form Action.
+	 *
+	 * The ACFE "Action name" Field has a pre-defined format, e.g. it must be
+	 * assigned the "acfe_slug" Field Type and have "acfe_form_custom_alias" as
+	 * its "name". Only its "placeholder" attribute needs to be configured.
+	 *
+	 * @since 0.1
+	 *
+	 * @return array $fields The array of Fields for this section.
+	 */
+	public function tab_action_add() {
+
+		// Init Fields array.
+		$fields = [];
+
+		// "Action" Tab wrapper.
+		$fields[] = [
+			'key' => $this->field_key . 'tab_action',
+			'label' => __( 'Action', 'conditional-form-actions-for-acfe' ),
+			'name' => '',
+			'type' => 'tab',
+			'instructions' => '',
+			'required' => 0,
+			'conditional_logic' => 0,
+			'wrapper' => [
+				'width' => '',
+				'class' => '',
+				'id' => '',
+				'data-no-preference' => true,
+			],
+			'acfe_permissions' => '',
+			'placement' => 'top',
+			'endpoint' => 0,
+		];
+
+		// "Action name" Field.
+		$fields[] = [
+			'key' => $this->field_key . 'custom_alias',
+			'label' => __( 'Action name', 'conditional-form-actions-for-acfe' ),
+			'name' => 'acfe_form_custom_alias',
+			'type' => 'acfe_slug',
+			'instructions' => __( '(Required) Name this action so it can be referenced.', 'conditional-form-actions-for-acfe' ),
+			'required' => 1,
+			'conditional_logic' => 0,
+			'wrapper' => [
+				'width' => '',
+				'class' => '',
+				'id' => '',
+				'data-instruction-placement' => 'field',
+			],
+			'acfe_permissions' => '',
+			'default_value' => '',
+			'placeholder' => $this->alias_placeholder,
+			'prepend' => '',
+			'append' => '',
+			'maxlength' => '',
+		];
+
+		// Add any further Fields.
+		$action_extras = $this->tab_action_append();
+		if ( ! empty( $action_extras ) ) {
+			$fields = array_merge(
+				$fields,
+				$action_extras
+			);
+		}
+
+		// --<
+		return $fields;
+
+	}
+
+	/**
+	 * Defines additional Fields for the "Action" Tab.
+	 *
+	 * @since 0.1
+	 *
+	 * @return array $fields The array of Fields for this section.
+	 */
+	public function tab_action_append() {
+		$fields = [];
+		return $fields;
+	}
+
+	/**
+	 * Defines the "Mapping" Tab.
+	 *
+	 * @since 0.1
+	 *
+	 * @return array $fields The array of Fields for this section.
+	 */
+	public function tab_mapping_add() {
+		$fields = [];
+		return $fields;
+	}
+
+	/**
+	 * Defines the "Mapping" Tab Header.
+	 *
+	 * @since 0.1
+	 *
+	 * @return array $fields The array of Fields for this section.
+	 */
+	public function tab_mapping_header() {
+
+		// "Mapping" Tab wrapper.
+		$mapping_tab = [ [
+			'key' => $this->field_key . 'tab_load',
+			'label' => __( 'Mapping', 'conditional-form-actions-for-acfe' ),
+			'name' => '',
+			'type' => 'tab',
+			'instructions' => '',
+			'required' => 0,
+			'conditional_logic' => 0,
+			'wrapper' => [
+				'width' => '',
+				'class' => '',
+				'id' => '',
+				'data-no-preference' => true,
+			],
+			'acfe_permissions' => '',
+			'placement' => 'top',
+			'endpoint' => 0,
+		] ];
+
+		// Combine Fields.
+		$fields = array_merge(
+			$mapping_tab
+		);
+
+		// --<
+		return $fields;
+
+	}
+
+	/**
+	 * Gets the array that defines a "Map Field" for the "Mapping" Tab.
+	 *
+	 * @since 0.1
+	 *
+	 * @param string $code The unique code for the Field.
+	 * @param string $label The label for the Field.
+	 * @param array $conditional_logic The conditional logic for the Field.
+	 * @return array $field The array of Field data.
+	 */
+	public function mapping_field_get( $code, $label, $conditional_logic = [] ) {
+
+		// Build the Field array.
+		$field = [
+			'key' => $this->field_key . 'map_' . $code,
+			'label' => $label,
+			'name' => $this->field_name . 'map_' . $code,
+			'type' => 'select',
+			'instructions' => '',
+			'required' => 0,
+			'wrapper' => [
+				'width' => '',
+				'class' => '',
+				'id' => '',
+			],
+			'acfe_permissions' => '',
+			'choices' => [],
+			'default_value' => [],
+			'allow_null' => 1,
+			'multiple' => 0,
+			'ui' => 1,
+			'return_format' => 'value',
+			'placeholder' => __( 'Default', 'conditional-form-actions-for-acfe' ),
+			'ajax' => 0,
+			'search_placeholder' => __( 'Enter a custom value or template tag. (See "Cheatsheet" tab)', 'conditional-form-actions-for-acfe' ),
+			'allow_custom' => 1,
+		];
+
+		// Default conditional logic.
+		$field['conditional_logic'] = 0;
+
+		// Maybe replace with custom conditional logic.
+		if ( ! empty( $conditional_logic ) ) {
+			$field['conditional_logic'] = $conditional_logic;
+		}
+
+		// --<
+		return $field;
+
+	}
+
+	/**
+	 * Adds filters that configure "Mapping Fields" when loaded.
+	 *
+	 * @since 0.1
+	 *
+	 * @param string $code The unique code for the Field.
+	 */
+	public function mapping_field_filters_add( $code ) {
+
+		// Grab reference to ACFE Helper object.
+		$helpers = acf_get_instance( 'acfe_dynamic_forms_helpers' );
+
+		// Populate mapping Fields.
+        add_filter( 'acf/prepare_field/name=' . $this->field_name . 'map_' . $code, [ $helpers, 'map_fields_deep_no_custom' ] );
+
+	}
+
+	/**
+	 * Prepare the data from an ACFE Form.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $form_data The array of data from the ACFE Form.
+	 * @return array $filtered_data The filtered data.
+	 */
+	public function form_data_prepare( $form_data ) {
+
+		// Init filtered data.
+		$filtered_data = [];
+
+		// Bail if we have no Form data to save.
+		if ( empty( $form_data ) ) {
+			return $filtered_data;
+		}
+
+		// Populate Activity data from the Form data.
+		foreach ( $form_data as $param => $value ) {
+			// Allow (string) "0" as valid data.
+			if ( ! empty( $value ) || $value === '0' ) {
+				$filtered_data[$param] = $value;
+			}
+		}
+
+		// --<
+		return $filtered_data;
+
+	}
+
+} // Class ends.
diff --git a/includes/form-actions/cfafa-form-action-email.php b/includes/form-actions/cfafa-form-action-email.php
new file mode 100644
index 0000000..fbca6e2
--- /dev/null
+++ b/includes/form-actions/cfafa-form-action-email.php
@@ -0,0 +1,815 @@
+<?php
+/**
+ * "Conditional Email" ACFE Form Action Class.
+ *
+ * Handles the "Conditional Email" ACFE Form Action.
+ *
+ * @package Conditional_Form_Actions_For_ACFE
+ * @since 0.1
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * CiviCRM Profile Sync "Conditional Email" ACFE Form Action Class.
+ *
+ * A class that handles the "Conditional Email" ACFE Form Action.
+ *
+ * @since 0.1
+ */
+class CFAFA_Form_Action_Email extends CFAFA_Form_Action_Base {
+
+	/**
+	 * Plugin object.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var object $plugin The plugin object.
+	 */
+	public $plugin;
+
+	/**
+	 * Parent (calling) object.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var object $acf The parent object.
+	 */
+	public $acfe;
+
+	/**
+	 * Form Action Name.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var string $action_name The unique name of the Form Action.
+	 */
+	public $action_name = 'email_cfafa';
+
+	/**
+	 * Field Key Prefix.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var string $field_key The prefix for the Field Key.
+	 */
+	public $field_key = 'field_cfafa_email_';
+
+	/**
+	 * Field Name Prefix.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var string $field_name The prefix for the Field Name.
+	 */
+	public $field_name = 'cfafa_email_';
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 0.1
+	 *
+	 * @param object $parent The parent object reference.
+	 */
+	public function __construct( $parent ) {
+
+		// Store references to objects.
+		$this->plugin = $parent->plugin;
+		$this->acfe = $parent;
+
+		// Label this Form Action.
+		$this->action_label = __( 'Conditional Email action', 'conditional-form-actions-for-acfe' );
+
+		// Alias Placeholder for this Form Action.
+		$this->alias_placeholder = __( 'Conditional Email', 'conditional-form-actions-for-acfe' );
+
+		// Init parent.
+		parent::__construct();
+
+	}
+
+	/**
+	 * Configure this object.
+	 *
+	 * @since 0.1
+	 */
+	public function configure() {
+
+		// Declare the mapped Email Fields with translatable titles.
+		$this->mapped_email_fields = [
+			'subject' => __( 'Subject', 'conditional-form-actions-for-acfe' ),
+			'from_name' => __( 'From Name', 'conditional-form-actions-for-acfe' ),
+			'from_email' => __( 'From Email', 'conditional-form-actions-for-acfe' ),
+			'alternative_receiver_address' => __( 'Alternative Receiver Address', 'conditional-form-actions-for-acfe' ),
+			'cc' => __( 'Carbon Copy', 'conditional-form-actions-for-acfe' ),
+			'bcc' => __( 'Blind Carbon Copy', 'conditional-form-actions-for-acfe' ),
+			//'extra_data' => __( 'Extra Data', 'conditional-form-actions-for-acfe' ),
+			//'from_email_option' => __( 'From Email Option', 'conditional-form-actions-for-acfe' ),
+		];
+
+		// Populate mapping Fields.
+		foreach ( $this->mapped_email_fields as $name => $title ) {
+			$this->mapping_field_filters_add( $name );
+		}
+
+		// Declare the Email Contact Fields with translatable titles.
+		$this->contact_fields = [
+			'contact_id' => __( 'Recipient CiviCRM Contact', 'conditional-form-actions-for-acfe' ),
+		];
+
+		// Handle Contact Fields.
+		foreach ( $this->contact_fields as $name => $title ) {
+
+			// Populate mapping Fields.
+			$this->mapping_field_filters_add( $name );
+
+			// Add Contact Action Reference Field to ACF Model.
+			$this->js_model_contact_reference_field_add( $this->field_name . 'ref_' . $name );
+
+			// Pre-load with "Generic" values.
+			//$filter = 'acf/prepare_field/name=' . $this->field_name . 'map_' . $name;
+			//add_filter( $filter, [ $this, 'prepare_choices' ], 5 );
+
+		}
+
+		// Email Conditional Field.
+		$this->mapping_field_filters_add( 'email_conditional' );
+
+	}
+
+	/**
+	 * Pre-load mapping Fields with "Generic" choices.
+	 *
+	 * Not used but leaving this here for future use.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $field The existing array of Field data.
+	 * @param array $field The modified array of Field data.
+	 */
+	public function prepare_choices( $field ) {
+
+		// --<
+		return $field;
+
+	}
+
+	/**
+	 * Performs validation when the Form the Action is attached to is submitted.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $form The array of Form data.
+	 * @param integer $current_post_id The ID of the Post from which the Form has been submitted.
+	 * @param string $action The customised name of the action.
+	 */
+	public function validation( $form, $current_post_id, $action ) {
+
+		/*
+		// Get some Form details.
+		$form_name = acf_maybe_get( $form, 'name' );
+		$form_id = acf_maybe_get( $form, 'ID' );
+		//acfe_add_validation_error( $selector, $message );
+		*/
+
+	}
+
+	/**
+	 * Performs the action when the Form the Action is attached to is submitted.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $form The array of Form data.
+	 * @param integer $current_post_id The ID of the Post from which the Form has been submitted.
+	 * @param string $action The customised name of the action.
+	 */
+	public function make( $form, $current_post_id, $action ) {
+
+		// Bail if a filter has overridden the action.
+		if ( false === $this->make_skip( $form, $current_post_id, $action ) ) {
+			return;
+		}
+
+		// Get some Form details.
+		$form_name = acf_maybe_get( $form, 'name' );
+		$form_id = acf_maybe_get( $form, 'ID' );
+
+		// Populate Email data array.
+		$email = $this->form_email_data( $form, $current_post_id, $action );
+
+		// Send the Email with the data from the Form.
+		$email = $this->form_email_save( $email );
+
+		// Save the results of this Action for later use.
+		$this->make_action_save( $action, $email );
+
+	}
+
+	/**
+	 * Defines additional Fields for the "Action" Tab.
+	 *
+	 * @since 0.1
+	 *
+	 * @return array $fields The array of Fields for this section.
+	 */
+	public function tab_action_append() {
+
+		// Define Template Field.
+		$template_field = [
+			'key' => $this->field_key . 'template',
+			'label' => __( 'Message Template', 'conditional-form-actions-for-acfe' ),
+			'name' => $this->field_name . 'template',
+			'type' => 'select',
+			'instructions' => '',
+			'required' => 0,
+			'conditional_logic' => 0,
+			'wrapper' => [
+				'width' => '',
+				'class' => '',
+				'id' => '',
+				'data-instruction-placement' => 'field',
+			],
+			'acfe_permissions' => '',
+			'default_value' => '',
+			'placeholder' => '',
+			'allow_null' => 0,
+			'multiple' => 0,
+			'ui' => 0,
+			'return_format' => 'value',
+			'choices' => $this->civicrm->email->template_options_get(),
+		];
+
+		// Define "Disable Smarty" Field.
+		$smarty_field = [
+			'key' => $this->field_key . 'disable_smarty',
+			'label' => __( 'Disable Smarty', 'conditional-form-actions-for-acfe' ),
+			'name' => $this->field_name . 'disable_smarty',
+			'type' => 'true_false',
+			'instructions' => __( 'Disable Smarty. Normal CiviMail tokens are still supported. By default Smarty is enabled if configured by CIVICRM_MAIL_SMARTY.', 'conditional-form-actions-for-acfe' ),
+			'required' => 0,
+			'conditional_logic' => 0,
+			'wrapper' => [
+				'width' => '',
+				'class' => '',
+				'id' => '',
+				'data-instruction-placement' => 'field',
+			],
+			'acfe_permissions' => '',
+			'message' => '',
+			'default_value' => 0,
+			'ui' => 1,
+			'ui_on_text' => '',
+			'ui_off_text' => '',
+		];
+
+		// Define "Create Activity" Field.
+		$create_activity_field = [
+			'key' => $this->field_key . 'create_activity',
+			'label' => __( 'Create Activity', 'conditional-form-actions-for-acfe' ),
+			'name' => $this->field_name . 'create_activity',
+			'type' => 'true_false',
+			'instructions' => __( 'Usually an Email Activity is created when an Email is sent.', 'conditional-form-actions-for-acfe' ),
+			'required' => 0,
+			'conditional_logic' => 0,
+			'wrapper' => [
+				'width' => '',
+				'class' => '',
+				'id' => '',
+				'data-instruction-placement' => 'field',
+			],
+			'acfe_permissions' => '',
+			'message' => '',
+			'default_value' => 1,
+			'ui' => 1,
+			'ui_on_text' => '',
+			'ui_off_text' => '',
+		];
+
+		// Define "Activity Details" Field.
+		$activity_details_field = [
+			'key' => $this->field_key . 'activity_details',
+			'label' => __( 'Activity Details', 'conditional-form-actions-for-acfe' ),
+			'name' => $this->field_name . 'activity_details',
+			'type' => 'select',
+			'instructions' => '',
+			'required' => 0,
+			'wrapper' => [
+				'width' => '',
+				'class' => '',
+				'id' => '',
+				'data-instruction-placement' => 'field',
+			],
+			'acfe_permissions' => '',
+			'default_value' => 'html,text',
+			'placeholder' => '',
+			'allow_null' => 0,
+			'multiple' => 0,
+			'ui' => 0,
+			'return_format' => 'value',
+			'choices' => [
+				'html,text' => __( 'HTML and Text versions of the body', 'conditional-form-actions-for-acfe' ),
+				'tplName' => __( 'Just the name of the message template', 'conditional-form-actions-for-acfe' ),
+				'html' => __( 'Just the HTML version of the body', 'conditional-form-actions-for-acfe' ),
+				'text' => __( 'Just the text version of the body', 'conditional-form-actions-for-acfe' ),
+			],
+			'conditional_logic' => [
+				[
+					[
+						'field' => $this->field_key . 'create_activity',
+						'operator' => '==',
+						'value' => 1,
+					],
+				],
+			],
+		];
+
+		// Init Fields.
+		$fields = [
+			$template_field,
+			$smarty_field,
+			$create_activity_field,
+			$activity_details_field,
+		];
+
+		// Add Case Field if the CiviCase component is active.
+		$case_active = $this->civicrm->is_component_enabled( 'CiviCase' );
+		if ( $case_active ) {
+
+			$fields[] = [
+				'key' => $this->field_key . 'email_case_id',
+				'label' => __( 'Case', 'conditional-form-actions-for-acfe' ),
+				'name' => $this->field_name . 'email_case_id',
+				'type' => 'cfafa_acfe_case_action_ref',
+				'instructions' => __( 'Select a Case Action in this Form.', 'conditional-form-actions-for-acfe' ),
+				'required' => 0,
+				'wrapper' => [
+					'width' => '',
+					'class' => '',
+					'id' => '',
+					'data-instruction-placement' => 'field',
+				],
+				'acfe_permissions' => '',
+				'default_value' => '',
+				'placeholder' => __( 'None', 'conditional-form-actions-for-acfe' ),
+				'allow_null' => 1,
+				'multiple' => 0,
+				'ui' => 0,
+				'return_format' => 'value',
+				'choices' => [],
+			];
+
+		}
+
+		// Add Conditional Field.
+		$code = 'email_conditional';
+		$label = __( 'Conditional On', 'conditional-form-actions-for-acfe' );
+		$conditional = $this->mapping_field_get( $code, $label );
+		$conditional['placeholder'] = __( 'Always send', 'conditional-form-actions-for-acfe' );
+		$conditional['wrapper']['data-instruction-placement'] = 'field';
+		$conditional['instructions'] = __( 'To send the Email only when a Form Field is populated (e.g. "First Name") link this to the Form Field. To send the Email only when more complex conditions are met, link this to a Hidden Field with value "1" where the conditional logic of that Field shows it when the conditions are met.', 'conditional-form-actions-for-acfe' );
+		$fields[] = $conditional;
+
+		// --<
+		return $fields;
+
+	}
+
+	/**
+	 * Defines the "Mapping" Tab.
+	 *
+	 * @since 0.1
+	 *
+	 * @return array $fields The array of Fields for this section.
+	 */
+	public function tab_mapping_add() {
+
+		// Get Tab Header.
+		$mapping_tab_header = $this->tab_mapping_header();
+
+		// Build Contacts Accordion.
+		$mapping_contacts_accordion = $this->tab_mapping_accordion_contacts_add();
+
+		// Build Email Details Accordion.
+		$mapping_email_accordion = $this->tab_mapping_accordion_email_add();
+
+		// Combine Sub-Fields.
+		$fields = array_merge(
+			$mapping_tab_header,
+			$mapping_contacts_accordion,
+			$mapping_email_accordion
+		);
+
+		// --<
+		return $fields;
+
+	}
+
+	/**
+	 * Defines the Fields in the "Contacts" Accordion.
+	 *
+	 * @since 0.1
+	 *
+	 * @return array $fields The array of Fields for this section.
+	 */
+	public function tab_mapping_accordion_contacts_add() {
+
+		// Init return.
+		$fields = [];
+
+		// "Recipient Contact Reference" Accordion wrapper open.
+		$fields[] = [
+			'key' => $this->field_key . 'mapping_accordion_contacts_open',
+			'label' => __( 'Recipient Contact Reference', 'conditional-form-actions-for-acfe' ),
+			'name' => '',
+			'type' => 'accordion',
+			'instructions' => '',
+			'required' => 0,
+			'conditional_logic' => 0,
+			'wrapper' => [
+				'width' => '',
+				'class' => '',
+				'id' => '',
+			],
+			'acfe_permissions' => '',
+			'open' => 0,
+			'multi_expand' => 1,
+			'endpoint' => 0,
+		];
+
+		// Add Contact Reference Fields.
+		foreach ( $this->contact_fields as $name => $title ) {
+
+			// Bundle them into a container group.
+			$contact_group_field = [
+				'key' => $this->field_key . 'contact_group_' . $name,
+				'label' => $title,
+				'name' => $this->field_name . 'contact_group_' . $name,
+				'type' => 'group',
+				'instructions' => sprintf( __( 'Use one Field to identify the %s.', 'conditional-form-actions-for-acfe' ), $title ),
+				'wrapper' => [
+					'width' => '',
+					'class' => '',
+					'id' => '',
+				],
+				'required' => 0,
+				'layout' => 'block',
+			];
+
+			// Define Contact Action Reference Field.
+			$contact_group_field['sub_fields'][] = [
+				'key' => $this->field_key . 'ref_' . $name,
+				'label' => __( 'CiviCRM Contact Action', 'conditional-form-actions-for-acfe' ),
+				'name' => $this->field_name . 'ref_' . $name,
+				'type' => 'cfafa_acfe_contact_action_ref',
+				'instructions' => __( 'Select a Contact Action in this Form.', 'conditional-form-actions-for-acfe' ),
+				'required' => 0,
+				'wrapper' => [
+					'width' => '',
+					'class' => '',
+					'id' => '',
+				],
+				'acfe_permissions' => '',
+				'default_value' => '',
+				'placeholder' => __( 'None', 'conditional-form-actions-for-acfe' ),
+				'allow_null' => 0,
+				'multiple' => 0,
+				'ui' => 0,
+				'return_format' => 'value',
+				'choices' => [],
+				'conditional_logic' => [
+					[
+						[
+							'field' => $this->field_key . 'map_' . $name,
+							'operator' => '==empty',
+						],
+						[
+							'field' => $this->field_key . 'cid_' . $name,
+							'operator' => '==empty',
+						],
+					],
+				],
+			];
+
+			// Define Contact ID Field.
+			$cid_field = [
+				'key' => $this->field_key . 'cid_' . $name,
+				'label' => __( 'CiviCRM Contact ID', 'conditional-form-actions-for-acfe' ),
+				'name' => $this->field_name . 'cid_' . $name,
+				'type' => 'civicrm_contact',
+				'instructions' => __( 'Select a CiviCRM Contact ID from the database.', 'conditional-form-actions-for-acfe' ),
+				'required' => 0,
+				'wrapper' => [
+					'width' => '',
+					'class' => '',
+					'id' => '',
+				],
+				'acfe_permissions' => '',
+				'default_value' => '',
+				'placeholder' => __( 'None', 'conditional-form-actions-for-acfe' ),
+				'allow_null' => 0,
+				'multiple' => 0,
+				'ui' => 0,
+				'return_format' => 'value',
+				'choices' => [],
+				'conditional_logic' => [
+					[
+						[
+							'field' => $this->field_key . 'ref_' . $name,
+							'operator' => '==empty',
+						],
+						[
+							'field' => $this->field_key . 'map_' . $name,
+							'operator' => '==empty',
+						],
+					],
+				],
+			];
+
+			// Add Contact ID Field.
+			$contact_group_field['sub_fields'][] = $cid_field;
+
+			// Define Custom Contact Reference Field.
+			$title = sprintf( __( 'Custom Contact Reference', 'conditional-form-actions-for-acfe' ), $title );
+			$mapping_field = $this->mapping_field_get( $name, $title );
+			$mapping_field['instructions'] = __( 'Define a custom Contact Reference.', 'conditional-form-actions-for-acfe' );
+			$mapping_field['conditional_logic'] = [
+				[
+					[
+						'field' => $this->field_key . 'ref_' . $name,
+						'operator' => '==empty',
+					],
+					[
+						'field' => $this->field_key . 'cid_' . $name,
+						'operator' => '==empty',
+					],
+				],
+			];
+
+			// Add Custom Contact Reference Field.
+			$contact_group_field['sub_fields'][] = $mapping_field;
+
+			// Add Contact Reference Group.
+			$fields[] = $contact_group_field;
+
+		}
+
+		// "Recipient Contact Reference" Accordion wrapper close.
+		$fields[] = [
+			'key' => $this->field_key . 'mapping_accordion_contacts_close',
+			'label' => __( 'Recipient Contact Reference', 'conditional-form-actions-for-acfe' ),
+			'name' => '',
+			'type' => 'accordion',
+			'instructions' => '',
+			'required' => 0,
+			'conditional_logic' => 0,
+			'wrapper' => [
+				'width' => '',
+				'class' => '',
+				'id' => '',
+			],
+			'acfe_permissions' => '',
+			'open' => 0,
+			'multi_expand' => 1,
+			'endpoint' => 1,
+		];
+
+		// --<
+		return $fields;
+
+	}
+
+	/**
+	 * Defines the Fields in the "Email Fields" Accordion.
+	 *
+	 * @since 0.1
+	 *
+	 * @return array $fields The array of Fields for this section.
+	 */
+	public function tab_mapping_accordion_email_add() {
+
+		// Init return.
+		$fields = [];
+
+		// "Email Fields" Accordion wrapper open.
+		$fields[] = [
+			'key' => $this->field_key . 'mapping_accordion_email_open',
+			'label' => __( 'Email Fields', 'conditional-form-actions-for-acfe' ),
+			'name' => '',
+			'type' => 'accordion',
+			'instructions' => '',
+			'required' => 0,
+			'conditional_logic' => 0,
+			'wrapper' => [
+				'width' => '',
+				'class' => '',
+				'id' => '',
+			],
+			'acfe_permissions' => '',
+			'open' => 0,
+			'multi_expand' => 1,
+			'endpoint' => 0,
+		];
+
+		// Add "Mapping" Fields.
+		foreach ( $this->mapped_email_fields as $name => $title ) {
+			$fields[] = $this->mapping_field_get( $name, $title );
+		}
+
+		// "Email Fields" Accordion wrapper close.
+		$fields[] = [
+			'key' => $this->field_key . 'mapping_accordion_email_close',
+			'label' => __( 'Email Fields', 'conditional-form-actions-for-acfe' ),
+			'name' => '',
+			'type' => 'accordion',
+			'instructions' => '',
+			'required' => 0,
+			'conditional_logic' => 0,
+			'wrapper' => [
+				'width' => '',
+				'class' => '',
+				'id' => '',
+			],
+			'acfe_permissions' => '',
+			'open' => 0,
+			'multi_expand' => 1,
+			'endpoint' => 1,
+		];
+
+		// --<
+		return $fields;
+
+	}
+
+	/**
+	 * Builds Email data array from mapped Fields.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $form The array of Form data.
+	 * @param integer $current_post_id The ID of the Post from which the Form has been submitted.
+	 * @param string $action The customised name of the action.
+	 * @return array $data The array of Email data.
+	 */
+	public function form_email_data( $form, $current_post_id, $action ) {
+
+		// Init data array.
+		$data = [];
+
+		// Get Field data.
+		$data['from'] = get_sub_field( $this->field_key . 'from' );
+		$data['from'] = acfe_form_map_field_value( $from, $current_post_id, $form );
+
+		$data['reply_to'] = get_sub_field( $this->field_key . 'reply_to' );
+		$data['reply_to'] = acfe_form_map_field_value( $reply_to, $current_post_id, $form );
+
+		$data['to'] = get_sub_field($this->field_key . 'to' );
+		$data['to'] = acfe_form_map_field_value( $to, $current_post_id, $form );
+
+		$data['cc'] = get_sub_field( $this->field_key . 'cc' );
+		$data['cc'] = acfe_form_map_field_value( $cc, $current_post_id, $form );
+
+		$data['bcc'] = get_sub_field( $this->field_key . 'bcc' );
+		$data['bcc'] = acfe_form_map_field_value( $bcc, $current_post_id, $form );
+
+		$data['subject'] = get_sub_field( $this->field_key . 'subject' );
+		$data['subject'] = acfe_form_map_field_value( $subject, $current_post_id, $form );
+
+		$data['content'] = get_sub_field( $this->field_key . 'content' );
+		$data['content'] = acfe_form_map_field_value( $content, $current_post_id, $form );
+
+		// Get Email Conditional Reference.
+		$data['email_conditional_ref'] = get_sub_field( $this->field_key . 'map_email_conditional' );
+		$conditionals = [ $data['email_conditional_ref'] ];
+
+		// Populate array with mapped Conditional Field values.
+		$conditionals = acfe_form_map_vs_fields( $conditionals, $conditionals, $current_post_id, $form );
+
+		// Save Email Conditional.
+		$data['email_conditional'] = array_pop( $conditionals );
+
+		// --<
+		return $data;
+
+	}
+
+	/**
+	 * Sends the Conditional Email given data from mapped Fields.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $email_data The array of Email data.
+	 * @return array|bool $email The Email data array, or false on failure.
+	 */
+	public function form_email_save( $email_data ) {
+
+		// Init return.
+		$email = false;
+
+		// Skip if the Email Conditional Reference Field has a value.
+		if ( ! empty( $email_data['email_conditional_ref'] ) ) {
+			// And the Email Conditional Field has no value.
+			if ( empty( $email_data['email_conditional'] ) ) {
+				return $email;
+			}
+		}
+
+		// Add Custom Field data if present.
+		if ( ! empty ( $custom_data ) ) {
+			$email_data += $custom_data;
+		}
+
+		// Unset Email Conditionals.
+		if ( isset( $email_data['email_conditional'] ) ) {
+			unset( $email_data['email_conditional'] );
+		}
+		if ( isset( $email_data['email_conditional_ref'] ) ) {
+			unset( $email_data['email_conditional_ref'] );
+		}
+
+		// Strip out empty Fields.
+		$email_data = $this->form_data_prepare( $email_data );
+
+		// Sanity checks.
+		if ( empty( $email_data['contact_id'] ) || empty( $email_data['template_id'] ) ) {
+			return $email;
+		}
+
+		// Send the Email.
+		$result = $this->civicrm->email->email_send( $email_data );
+
+		// Bail on failure.
+		if ( $result === false ) {
+			return $email;
+		}
+
+		// --<
+		return $result;
+
+	}
+
+	/**
+	 * Finds the linked Contact ID when it has been mapped.
+	 *
+	 * @since 0.1
+	 *
+	 * @param string $action_name The name of the referenced Form Action.
+	 * @return integer|bool $contact_id The numeric ID of the Contact, or false if not found.
+	 */
+	public function form_contact_id_get_mapped( $action_name ) {
+
+		// Init return.
+		$contact_id = false;
+
+		// We need an Action Name.
+		if ( empty( $action_name ) ) {
+			return $contact_id;
+		}
+
+		// Get the Contact data for that Action.
+		$related_contact = acfe_form_get_action( $action_name, 'contact' );
+		if ( empty( $related_contact['id'] ) ) {
+			return $contact_id;
+		}
+
+		// Assign return.
+		$contact_id = (int) $related_contact['id'];
+
+		// --<
+		return $contact_id;
+
+	}
+
+	/**
+	 * Finds the linked Case ID when it has been mapped.
+	 *
+	 * @since 0.1
+	 *
+	 * @param string $action_name The name of the referenced Form Action.
+	 * @return integer|bool $case_id The numeric ID of the Case, or false if not found.
+	 */
+	public function form_case_id_get_mapped( $action_name ) {
+
+		// Init return.
+		$case_id = false;
+
+		// We need an Action Name.
+		if ( empty( $action_name ) ) {
+			return $case_id;
+		}
+
+		// Get the Case ID for that Action.
+		$related_case_id = acfe_form_get_action( $action_name, 'id' );
+		if ( empty( $related_case_id ) ) {
+			return $case_id;
+		}
+
+		// Assign return.
+		$case_id = (int) $related_case_id;
+
+		// --<
+		return $case_id;
+
+	}
+
+} // Class ends.
diff --git a/includes/form-actions/cfafa-form-action-product.php b/includes/form-actions/cfafa-form-action-product.php
new file mode 100644
index 0000000..975862e
--- /dev/null
+++ b/includes/form-actions/cfafa-form-action-product.php
@@ -0,0 +1,349 @@
+<?php
+/**
+ * "WooCommerce Product" ACFE Form Action Class.
+ *
+ * Handles the "WooCommerce Product" ACFE Form Action.
+ *
+ * @package Conditional_Form_Actions_For_ACFE
+ * @since 0.1
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * CiviCRM Profile Sync "WooCommerce Product" ACFE Form Action Class.
+ *
+ * A class that handles the "WooCommerce Product" ACFE Form Action.
+ *
+ * @since 0.1
+ */
+class CFAFA_Form_Action_Product extends CFAFA_Form_Action_Base {
+
+	/**
+	 * Plugin object.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var object $plugin The plugin object.
+	 */
+	public $plugin;
+
+	/**
+	 * Parent (calling) object.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var object $acf The parent object.
+	 */
+	public $acfe;
+
+	/**
+	 * Form Action Name.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var string $action_name The unique name of the Form Action.
+	 */
+	public $action_name = 'woo_cfafa_product';
+
+	/**
+	 * Field Key Prefix.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var string $field_key The prefix for the Field Key.
+	 */
+	public $field_key = 'field_cfafa_woo_product_';
+
+	/**
+	 * Field Name Prefix.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var string $field_name The prefix for the Field Name.
+	 */
+	public $field_name = 'cfafa_woo_product_';
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 0.1
+	 *
+	 * @param object $parent The parent object reference.
+	 */
+	public function __construct( $parent ) {
+
+		// Store references to objects.
+		$this->plugin = $parent->plugin;
+		$this->acfe = $parent;
+
+		// Label this Form Action.
+		$this->action_label = __( 'WooCommerce Product action', 'conditional-form-actions-for-acfe' );
+
+		// Alias Placeholder for this Form Action.
+		$this->alias_placeholder = __( 'WooCommerce Product', 'conditional-form-actions-for-acfe' );
+
+		// Init parent.
+		parent::__construct();
+
+	}
+
+	/**
+	 * Configure this object.
+	 *
+	 * @since 0.1
+	 */
+	public function configure() {
+
+		// WooCommerce Product Conditional Field.
+		$this->mapping_field_filters_add( 'product_conditional' );
+
+	}
+
+	/**
+	 * Performs the action when the Form the Action is attached to is submitted.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $form The array of Form data.
+	 * @param integer $current_post_id The ID of the Post from which the Form has been submitted.
+	 * @param string $action The customised name of the action.
+	 */
+	public function make( $form, $current_post_id, $action ) {
+
+		// Bail if a filter has overridden the action.
+		if ( false === $this->make_skip( $form, $current_post_id, $action ) ) {
+			return;
+		}
+
+		// Get some Form details.
+		$form_name = acf_maybe_get( $form, 'name' );
+		$form_id = acf_maybe_get( $form, 'ID' );
+
+		// Populate Product data array.
+		$product_data = $this->form_data_get( $form, $current_post_id, $action );
+
+		// Act with the data from the Form.
+		$product_data = $this->form_product_save( $product_data );
+
+		// Save the results of this Action for later use.
+		$this->make_action_save( $action, $product_data );
+
+	}
+
+	/**
+	 * Defines additional Fields for the "Action" Tab.
+	 *
+	 * @since 0.1
+	 *
+	 * @return array $fields The array of Fields for this section.
+	 */
+	public function tab_action_append() {
+
+		// Init Fields.
+		$fields = [];
+
+		$fields[] = [
+			'key' => $this->field_key . 'product_id',
+			'label' => __( 'WooCommerce Product', 'conditional-form-actions-for-acfe' ),
+			'name' => $this->field_name . 'product_id',
+			'type' => 'select',
+			'instructions' => __( 'Use this to add a WooCommerce Product to the Cart.', 'conditional-form-actions-for-acfe' ),
+			'required' => 0,
+			'conditional_logic' => 0,
+			'wrapper' => [
+				'width' => '',
+				'class' => '',
+				'id' => '',
+				'data-instruction-placement' => 'field',
+			],
+			'acfe_permissions' => '',
+			'default_value' => '',
+			'placeholder' => '',
+			'allow_null' => 1,
+			'multiple' => 0,
+			'ui' => 0,
+			//'ajax' => 1,
+			//'ajax_action' => 'cfafa_get_products',
+			'return_format' => 'value',
+			'choices' => $this->product_choices_get(),
+		];
+
+		// Add Conditional Field.
+		$code = 'product_conditional';
+		$label = __( 'Conditional On', 'conditional-form-actions-for-acfe' );
+		$conditional = $this->mapping_field_get( $code, $label );
+		$conditional['placeholder'] = __( 'Always add', 'conditional-form-actions-for-acfe' );
+		$conditional['wrapper']['data-instruction-placement'] = 'field';
+		$conditional['instructions'] = __( 'To add the Product to the Cart only when a Form Field is populated (e.g. "First Name") link this to the Form Field. To add the Product to the Cart only when more complex conditions are met, link this to a Hidden Field with value "1" where the conditional logic of that Field shows it when the conditions are met.', 'conditional-form-actions-for-acfe' );
+		$fields[] = $conditional;
+
+		// --<
+		return $fields;
+
+	}
+
+	/**
+	 * Builds Product data array from mapped Fields.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $form The array of Form data.
+	 * @param integer $current_post_id The ID of the Post from which the Form has been submitted.
+	 * @param string $action The customised name of the action.
+	 * @return array $data The array of Product data.
+	 */
+	public function form_data_get( $form, $current_post_id, $action ) {
+
+		// Add Product ID.
+		$data['product_id'] = get_sub_field( $this->field_key . 'product_id' );
+
+		// Get Product Conditional Reference.
+		$data['product_conditional_ref'] = get_sub_field( $this->field_key . 'map_product_conditional' );
+		$conditionals = [ $data['product_conditional_ref'] ];
+
+		// Populate array with mapped Conditional Field values.
+		$conditionals = acfe_form_map_vs_fields( $conditionals, $conditionals, $current_post_id, $form );
+
+		// Save Product Conditional.
+		$data['product_conditional'] = array_pop( $conditionals );
+
+		// --<
+		return $data;
+
+	}
+
+	/**
+	 * Sends the WooCommerce Product given data from mapped Fields.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $product_data The array of Product data.
+	 * @return array|bool $product_data The Product data array, or false on failure.
+	 */
+	public function form_product_save( $product_data ) {
+
+		// Skip if the Product Conditional Reference Field has a value.
+		if ( ! empty( $product_data['product_conditional_ref'] ) ) {
+			// And the Product Conditional Field has no value.
+			if ( empty( $product_data['product_conditional'] ) ) {
+				return $product_data;
+			}
+		}
+
+		// Unset Product Conditionals.
+		if ( isset( $product_data['product_conditional'] ) ) {
+			unset( $product_data['product_conditional'] );
+		}
+		if ( isset( $product_data['product_conditional_ref'] ) ) {
+			unset( $product_data['product_conditional_ref'] );
+		}
+
+		// Strip out empty Fields.
+		$product_data = $this->form_data_prepare( $product_data );
+
+		// Sanity check.
+		if ( empty( $product_data['product_id'] ) ) {
+			return false;
+		}
+
+		// Add the Product to the WooCommerce Cart.
+		$result = $this->product_add_to_cart( $product_data );
+
+		// Bail on failure.
+		if ( $result === false ) {
+			return false;
+		}
+
+		// Add Cart Item key to Product data.
+		$product_data['cart_item_key'] = $result;
+
+		// --<
+		return $product_data;
+
+	}
+
+	/**
+	 * Gets the WooCommerce Products as an array of options for ACF.
+	 *
+	 * @since 0.1
+	 *
+	 * @return array $options The array of Products formatted for ACF.
+	 */
+	public function product_choices_get() {
+
+		// Init return.
+		$options = [];
+
+		/*
+		 * Build params to get all published Products.
+		 *
+		 * We have to query directly because WooCommerce has not been initialised
+		 * at this point and wc_get_products() does not return any results. This
+		 * is because ACF hooks into `init` with priority 5 *before* WooCommerce
+		 * initialises.
+		 *
+		 * This may need to be looked at again in future.
+		 */
+		$args = [
+			'post_type' => 'product',
+			'post_status' => [ 'publish' ],
+			'no_found_rows' => true,
+			'posts_per_page' => -1,
+		];
+
+		// Do query.
+		$query = new WP_Query( $args );
+
+		// Do the loop.
+		if ( $query->have_posts() ) {
+			foreach ( $query->get_posts() as $found ) {
+				$options[ $found->ID ] = $found->post_title;
+			}
+		}
+
+		// Reset Post data just in case.
+		wp_reset_postdata();
+
+		// --<
+		return $options;
+
+	}
+
+	/**
+	 * Adds the Product to the Cart.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $product_data The array of Product data.
+	 * @return integer|bool $cart_item_key The key of the Cart Item, or false otherwise.
+	 */
+	public function product_add_to_cart( $product_data ) {
+
+		// Get the Product.
+		$product = wc_get_product( $product_data['product_id'] );
+
+		// Sanity check.
+		if ( empty( $product ) ) {
+			return false;
+		}
+
+		// Some defaults.
+		$product_id = $product->get_id();
+		$quantity = 1;
+		$variation_id = 0;
+		$variation = [];
+		$cart_item_data = [];
+
+		// Build Cart Item data?
+
+ 		// Add the configured Product to the Cart.
+ 		$cart_item_key = WC()->cart->add_to_cart( $product_id, $quantity, $variation_id, $variation, $cart_item_data );
+
+ 		// --<
+ 		return $cart_item_key;
+
+	}
+
+} // Class ends.
diff --git a/includes/form-actions/cfafa-form-action-redirect.php b/includes/form-actions/cfafa-form-action-redirect.php
new file mode 100644
index 0000000..42c7734
--- /dev/null
+++ b/includes/form-actions/cfafa-form-action-redirect.php
@@ -0,0 +1,278 @@
+<?php
+/**
+ * "CiviCRM Redirect" ACFE Form Action Class.
+ *
+ * Handles the "CiviCRM Redirect" ACFE Form Action.
+ *
+ * @package Conditional_Form_Actions_For_ACFE
+ * @since 0.1
+ */
+
+// Exit if accessed directly.
+defined( 'ABSPATH' ) || exit;
+
+/**
+ * CiviCRM Profile Sync "CiviCRM Redirect" ACFE Form Action Class.
+ *
+ * A class that handles the "CiviCRM Redirect" ACFE Form Action.
+ *
+ * @since 0.1
+ */
+class CFAFA_Form_Action_Redirect extends CFAFA_Form_Action_Base {
+
+	/**
+	 * Plugin object.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var object $plugin The plugin object.
+	 */
+	public $plugin;
+
+	/**
+	 * Parent (calling) object.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var object $acf The parent object.
+	 */
+	public $acfe;
+
+	/**
+	 * Form Action Name.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var string $action_name The unique name of the Form Action.
+	 */
+	public $action_name = 'redirect_cfafa';
+
+	/**
+	 * Field Key Prefix.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var string $field_key The prefix for the Field Key.
+	 */
+	public $field_key = 'field_cfafa_redirect_';
+
+	/**
+	 * Field Name Prefix.
+	 *
+	 * @since 0.1
+	 * @access public
+	 * @var string $field_name The prefix for the Field Name.
+	 */
+	public $field_name = 'cfafa_redirect_';
+
+	/**
+	 * Constructor.
+	 *
+	 * @since 0.1
+	 *
+	 * @param object $parent The parent object reference.
+	 */
+	public function __construct( $parent ) {
+
+		// Store references to objects.
+		$this->plugin = $parent->plugin;
+		$this->acfe = $parent;
+
+		// Label this Form Action.
+		$this->action_label = __( 'Conditional Redirect action', 'conditional-form-actions-for-acfe' );
+
+		// Alias Placeholder for this Form Action.
+		$this->alias_placeholder = __( 'Conditional Redirect', 'conditional-form-actions-for-acfe' );
+
+		// Init parent.
+		parent::__construct();
+
+	}
+
+	/**
+	 * Configure this object.
+	 *
+	 * @since 0.1
+	 */
+	public function configure() {
+
+		// Conditional Field.
+		$this->mapping_field_filters_add( 'redirect_conditional' );
+
+	}
+
+	/**
+	 * Performs the action when the Form the Action is attached to is submitted.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $form The array of Form data.
+	 * @param integer $current_post_id The ID of the Post from which the Form has been submitted.
+	 * @param string $action The customised name of the action.
+	 */
+	public function make( $form, $current_post_id, $action ) {
+
+		// Bail if a filter has overridden the action.
+		if ( false === $this->make_skip( $form, $current_post_id, $action ) ) {
+			return;
+		}
+
+		// Populate Redirect data array.
+		$conditional_redirect = $this->form_redirect_data( $form, $current_post_id, $action );
+
+		// Do the Redirect with the data from the Form.
+		$conditional_redirect = $this->form_redirect_perform( $conditional_redirect );
+
+		// Save the results of this Action for later use.
+		$this->make_action_save( $action, $conditional_redirect );
+
+	}
+
+	/**
+	 * Defines additional Fields for the "Action" Tab.
+	 *
+	 * @since 0.1
+	 *
+	 * @return array $fields The array of Fields for this section.
+	 */
+	public function tab_action_append() {
+
+		// Init Fields.
+		$fields = [];
+
+		// Add URL Field.
+		$fields[] = [
+			'key' => $this->field_key . 'redirect_url',
+			'label' => __( 'Redirect URL', 'conditional-form-actions-for-acfe' ),
+			'name' => $this->field_name . 'redirect_url',
+			'type' => 'text',
+			'instructions' => __( 'The URL to redirect to. See "Cheatsheet" tab for all available template tags.', 'conditional-form-actions-for-acfe' ),
+			'required' => 0,
+			'conditional_logic' => 0,
+			'wrapper' => [
+				'width' => '',
+				'class' => '',
+				'id' => '',
+				'data-instruction-placement' => 'field'
+			],
+			'acfe_permissions' => '',
+			'default_value' => '',
+			'placeholder' => '',
+			'prepend' => '',
+			'append' => '',
+			'maxlength' => '',
+		];
+
+		// Add Conditional Field.
+		$code = 'redirect_conditional';
+		$label = __( 'Conditional On', 'conditional-form-actions-for-acfe' );
+		$conditional = $this->mapping_field_get( $code, $label );
+		$conditional['placeholder'] = __( 'Always redirect', 'conditional-form-actions-for-acfe' );
+		$conditional['wrapper']['data-instruction-placement'] = 'field';
+		$conditional['instructions'] = __( 'To redirect only when a Form Field is populated, link this to the Form Field. To redirect only when more complex conditions are met, link this to a Hidden Field with value "1" where the conditional logic of that Field shows it when the conditions are met.', 'conditional-form-actions-for-acfe' );
+		$fields[] = $conditional;
+
+		// --<
+		return $fields;
+
+	}
+
+	/**
+	 * Builds Redirect data array from mapped Fields.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $form The array of Form data.
+	 * @param integer $current_post_id The ID of the Post from which the Form has been submitted.
+	 * @param string $action The customised name of the action.
+	 * @return array $data The array of Redirect data.
+	 */
+	public function form_redirect_data( $form, $current_post_id, $action ) {
+
+		// Get some Form details.
+		$form_name = acf_maybe_get( $form, 'name' );
+		$form_id = acf_maybe_get( $form, 'ID' );
+
+		// Init data array.
+		$data = [];
+
+		// Get the Action variables.
+        $url = get_sub_field( $this->field_key . 'redirect_url' );
+        $url = acfe_form_map_field_value( $url, $current_post_id, $form );
+
+		/**
+		 * Filter the Redirect URL.
+		 *
+		 * @since 0.1
+		 *
+		 * @param string $url The Redirect URL.
+		 * @param array $form The array of Form data.
+		 * @param integer $current_post_id The ID of the Post from which the Form has been submitted.
+		 * @param string $action The customised name of the Form Action.
+		 */
+		$filter = 'acfe/form/submit/' . $this->action_name . '/url';
+		$url = apply_filters( $filter, $url, $form, $current_post_id, $action );
+		$url = apply_filters( $filter . '/form=' . $form_name, $url, $form, $current_post_id, $action );
+		if ( ! empty( $action ) ) {
+			$url = apply_filters( $filter . '/action=' . $action, $url, $form, $current_post_id, $action );
+		}
+
+		// Add trimmed URL to the data array.
+		$data['redirect_url'] = trim( $url );
+
+		// Get Redirect Conditional Reference.
+		$data['redirect_conditional_ref'] = get_sub_field( $this->field_key . 'map_redirect_conditional' );
+		$conditionals = [ $data['redirect_conditional_ref'] ];
+
+		// Populate array with mapped Conditional Field values.
+		$conditionals = acfe_form_map_vs_fields( $conditionals, $conditionals, $current_post_id, $form );
+
+		// Save Redirect Conditional.
+		$data['redirect_conditional'] = array_pop( $conditionals );
+
+		// --<
+		return $data;
+
+	}
+
+	/**
+	 * Conditionally performs the redirect given data from mapped Fields.
+	 *
+	 * @since 0.1
+	 *
+	 * @param array $redirect_data The array of Redirect data.
+	 * @return array|bool $redirect The Redirect data array, or false on failure.
+	 */
+	public function form_redirect_perform( $redirect_data ) {
+
+		// Skip if the Redirect Conditional Reference Field has a value.
+		if ( ! empty( $redirect_data['redirect_conditional_ref'] ) ) {
+			// And the Redirect Conditional Field has no value.
+			if ( empty( $redirect_data['redirect_conditional'] ) ) {
+				return $redirect_data;
+			}
+		}
+
+		// Unset Redirect Conditionals.
+		if ( isset( $redirect_data['redirect_conditional'] ) ) {
+			unset( $redirect_data['redirect_conditional'] );
+		}
+		if ( isset( $redirect_data['redirect_conditional_ref'] ) ) {
+			unset( $redirect_data['redirect_conditional_ref'] );
+		}
+
+		// Strip out empty Fields.
+		$redirect_data = $this->form_data_prepare( $redirect_data );
+
+		// Sanity check.
+		if ( empty( $redirect_data['redirect_url'] ) ) {
+			return false;
+		}
+
+		// Do the redirect.
+		wp_redirect( $redirect_data['redirect_url'] );
+		exit;
+
+	}
+
+} // Class ends.
-- 
GitLab