<?php /** * "Conditional Email" ACFE Form Action Class. * * Handles the "Conditional Email" ACFE Form Action. * * @package Conditional_Form_Actions_For_ACFE */ // Exit if accessed directly. defined( 'ABSPATH' ) || exit; /** * "Conditional Email" ACFE Form Action Class. * * A class that handles the "Conditional Email" ACFE Form Action. * * @since 0.2.0 */ class CFAFA_ACFE_Form_Action_Email extends CFAFA_ACFE_Form_Action_Base { /** * Plugin object. * * @since 0.2.0 * @access public * @var Conditional_Form_Actions_For_ACFE */ public $plugin; /** * ACF Extended object. * * @since 0.2.0 * @access public * @var CFAFA_ACFE */ public $acfe; /** * Form Action Name. * * @since 0.2.0 * @access public * @var string */ public $name = 'email_cfafa'; /** * Field Key Prefix. * * @since 0.2.0 * @access public * @var string */ public $field_key = 'field_'; /** * Field Name Prefix. * * @since 0.2.0 * @access public * @var string */ public $field_name = ''; /** * Array of translatable mapped Email Fields. * * @since 0.2.0 * @access public * @var array */ public $mapped_email_fields = []; /** * Constructor. * * @since 0.2.0 */ public function __construct() { // Store references to objects. $this->plugin = cfafa(); $this->acfe = $this->plugin->acfe; // Label this Form Action. $this->title = __( 'Conditional Email action', 'conditional-form-actions-for-acfe' ); // Alias Placeholder for this Form Action. $this->alias_placeholder = __( 'Conditional Email', 'conditional-form-actions-for-acfe' ); // Declare the mapped Email Fields with translatable titles. $this->mapped_email_fields = [ 'from_name' => __( 'From Name', 'conditional-form-actions-for-acfe' ), 'from_email' => __( 'From Email', 'conditional-form-actions-for-acfe' ), 'reply_to_name' => __( 'Reply To Name', 'conditional-form-actions-for-acfe' ), 'reply_to_email' => __( 'Reply To Email', 'conditional-form-actions-for-acfe' ), 'to_name' => __( 'To Name', 'conditional-form-actions-for-acfe' ), 'to_email' => __( 'To Email', 'conditional-form-actions-for-acfe' ), 'cc' => __( 'Cc', 'conditional-form-actions-for-acfe' ), 'bcc' => __( 'Bcc', 'conditional-form-actions-for-acfe' ), 'subject' => __( 'Subject', 'conditional-form-actions-for-acfe' ), ]; // Declare core Fields for this Form Action. $this->item = [ 'action' => $this->name, 'name' => '', 'email' => [ 'from_name' => '', 'from_email' => '', 'to_name' => '', 'to_email' => '', 'reply_to_name' => '', 'reply_to_email' => '', 'cc' => '', 'bcc' => '', 'subject' => '', 'content' => '', 'html' => false, ], 'files' => [ 'dynamic' => [], 'static' => [], ], ]; // Init parent. parent::__construct(); } /** * Prepares data for saving when the Form Action is saved. * * @since 0.2.0 * * @param array $action The array of Form Action data. * @return array $save The array of data to save. */ public function prepare_save_action( $action ) { // Init with default array for this Field. $save = $this->item; // Always add action name. $save['name'] = $action['name']; // Always save Conditional Field. if ( acf_maybe_get( $action, $this->conditional_code ) ) { $save['conditional'] = $action[ $this->conditional_code ]; } // Email data. $keys = array_keys( $this->mapped_email_fields ); foreach ( $keys as $key ) { if ( acf_maybe_get( $action, $key ) ) { $save['email'][ $key ] = $action[ $key ]; } } // Get Content Type from Content Group. $group = $action['content_group']; if ( 'editor' === $group['content_type'] ) { $save['email']['content'] = $group['content_editor']; } elseif ( 'html' === $group['content_type'] ) { $save['email']['content'] = $group['content_html']; $save['email']['html'] = true; } // Dynamic Files. $save['files']['dynamic'] = acf_get_array( $action['files_dynamic'] ); // Static Files. $save['files']['static'] = acf_get_array( $action['files_static'] ); // --< return $save; } /** * Prepares data when the Form Action is loaded. * * @since 0.2.0 * * @param array $action The array of Form Action data. * @return array $action The array of data to save. */ public function prepare_load_action( $action ) { // Email data. $keys = array_keys( $this->mapped_email_fields ); foreach ( $keys as $key ) { if ( acf_maybe_get( $action['email'], $key ) ) { $action[ $key ] = $action['email'][ $key ]; } } // Populate Content Editor. $value = $action['email']['content']; $group = 'content_group'; if ( true === $action['email']['html'] ) { $action[ $group ]['content_type'] = 'html'; $action[ $group ]['content_html'] = $value; } else { $action[ $group ]['content_type'] = 'editor'; $action[ $group ]['content_editor'] = $value; } // Init Dynamic Files array. $action['files_dynamic'] = []; // Populate Dynamic Files. $files = $action['files']['dynamic']; foreach ( $files as $row ) { $action['files_dynamic'][] = [ 'file' => $row['file'], 'file_delete' => $row['file_delete'], ]; } // Init Static Files array. $action['files_static'] = []; // Populate Static Files. $files = $action['files']['static']; foreach ( $files as $row ) { $action['files_static'][] = [ 'file_static' => $row['file_static'], ]; } // --< return $action; } /** * Performs validation when the Form the Action is attached to is submitted. * * @since 0.2.0 * * @param array $form The array of Form data. * @param array $action The array of Action data. */ public function validation( $form, $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.2.0 * * @param array $form The array of Form data. * @param array $action The array of Action data. */ public function make_action( $form, $action ) { // Bail if a filter has overridden the action. if ( false === $this->make_skip( $form, $action ) ) { return; } // Populate Conditional Reference and value. $action = $this->form_conditional_populate( $action ); // Populate Email data array. $email = $this->form_email_data( $form, $action ); // Populate Attachments data array. $attachments = $this->form_attachments_data( $form, $action ); // Check Conditional. if ( $this->form_conditional_check( $action ) ) { // Build the arguments. $args = $this->form_build_args( $email, $attachments, $form, $action ); // Skip when the args have been overridden. if ( ! empty( $args ) ) { // Send the Email with the built arguments. $email = $this->form_email_send( $args, $email ); } } // Maybe remove Dynamic Attachments. $this->form_attachments_delete( $attachments ); // Build result. $result = [ 'email' => $email, 'attachments' => $attachments['attachments'], 'conditional' => $action['conditional'], ]; // Save the results of this Action for later use. $this->make_action_save( $action, $result ); } /** * Defines additional Fields for the "Action" Tab. * * @since 0.2.0 * * @return array $fields The array of Fields for this section. */ protected function tab_action_append() { // Init Fields. $fields = []; // Configure Conditional Field. $args = [ 'placeholder' => __( 'Always send', 'conditional-form-actions-for-acfe' ), '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' ), ]; // Add Conditional Field. $fields[] = $this->form_conditional_field_get( $args ); // --< return $fields; } /** * Defines the "Mapping" Tab. * * @since 0.2.0 * * @return array $fields The array of Fields for this section. */ protected function tab_mapping_add() { // Get Tab Header. $label = __( 'Email', 'conditional-form-actions-for-acfe' ); $mapping_tab_header = $this->tab_mapping_header( $label ); // Build Email Details Accordion. $mapping_email_accordion = $this->tab_mapping_accordion_email_add(); // Combine Sub-Fields. $fields = array_merge( $mapping_tab_header, $mapping_email_accordion ); // --< return $fields; } /** * Defines the Fields in the "Email Fields" Accordion. * * @since 0.2.0 * * @return array $fields The array of Fields for this section. */ private function tab_mapping_accordion_email_add() { // Init return. $fields = []; // Add "Mapping" Fields. foreach ( $this->mapped_email_fields as $name => $title ) { $fields[] = $this->mapping_field_get( $name, $title ); } // Define Content Group. $content_group = [ 'key' => $this->field_key . 'content_group', 'label' => __( 'Content', 'conditional-form-actions-for-acfe' ), 'name' => 'content_group', 'type' => 'group', 'instructions' => sprintf( /* translators: 1: An opening <code> tag, 2: A closing <code> tag, 3: A line break tag, */ __( 'Render customized email content.%3$s%3$sRender all fields values:%3$s%1$s{fields}%2$s%3$s%3$sRender field value:%3$s%1$s{field:field_abc123}%2$s%3$s%1$s{field:my_field}%2$s', 'conditional-form-actions-for-acfe' ), '<code>', '</code>', '<br />' ), 'required' => 0, 'conditional_logic' => 0, 'wrapper' => [ 'width' => '', 'class' => '', 'id' => '', ], 'layout' => 'block', 'acfe_seamless_style' => true, 'acfe_group_modal' => 0, 'sub_fields' => [], ]; // Define Content Type Field. $content_group['sub_fields'][] = [ 'key' => $this->field_key . 'content_type', 'label' => '', 'name' => 'content_type', 'type' => 'select', 'instructions' => '', 'required' => 0, 'conditional_logic' => 0, 'wrapper' => [ 'width' => '', 'class' => '', 'id' => '', ], 'choices' => [ 'editor' => __( 'Content Editor', 'conditional-form-actions-for-acfe' ), 'html' => __( 'Raw HTML', 'conditional-form-actions-for-acfe' ), ], 'default_value' => [ 'custom' ], 'allow_null' => 0, 'multiple' => 0, 'ui' => 0, 'return_format' => 'value', 'placeholder' => __( 'Default', 'conditional-form-actions-for-acfe' ), 'ajax' => 0, 'allow_custom' => 0, ]; // Define Content Editor Field. $content_group['sub_fields'][] = [ 'key' => $this->field_key . 'content_editor', 'label' => '', 'name' => 'content_editor', 'type' => 'wysiwyg', 'instructions' => '', 'required' => 0, 'conditional_logic' => [ [ [ 'field' => $this->field_key . 'content_type', 'operator' => '==', 'value' => 'editor', ], ], ], 'wrapper' => [ 'width' => '', 'class' => '', 'id' => '', ], 'default_value' => '', 'tabs' => 'all', 'toolbar' => 'full', 'media_upload' => 1, 'delay' => 0, ]; // Define Content HTML Field. $content_group['sub_fields'][] = [ 'key' => $this->field_key . 'content_html', 'label' => '', 'name' => 'content_html', 'type' => 'acfe_code_editor', 'instructions' => '', 'required' => 0, 'conditional_logic' => [ [ [ 'field' => $this->field_key . 'content_type', 'operator' => '==', 'value' => 'html', ], ], ], 'wrapper' => [ 'width' => '', 'class' => '', 'id' => '', ], 'default_value' => '', 'rows' => 18, ]; // Finally, add Content Group. $fields[] = $content_group; // --< return $fields; } /** * Defines the "Attachments" Tab. * * @since 0.2.0 * * @return array $fields The array of Fields for this section. */ protected function tab_attachments_add() { // Init tab array. $attachments_tab = []; // Add "Dynamic Files" Repeater. $attachments_tab[] = [ 'key' => $this->field_key . 'files_dynamic', 'label' => __( 'Dynamic Files', 'conditional-form-actions-for-acfe' ), 'name' => 'files_dynamic', 'type' => 'repeater', 'instructions' => '', 'required' => 0, 'conditional_logic' => 0, 'wrapper' => [ 'width' => '', 'class' => '', 'id' => '', ], 'collapsed' => '', 'min' => 0, 'max' => 0, 'layout' => 'table', 'button_label' => __( 'Add File', 'conditional-form-actions-for-acfe' ), 'sub_fields' => [ [ 'key' => $this->field_key . 'file', 'label' => __( 'File', 'conditional-form-actions-for-acfe' ), 'name' => 'file', 'type' => 'select', 'instructions' => '', 'required' => 0, 'wrapper' => [ 'width' => '', 'class' => '', 'id' => '', ], 'choices' => [], 'default_value' => [], 'allow_null' => 1, 'multiple' => 0, 'ui' => 1, 'return_format' => 'value', 'placeholder' => '', 'ajax' => 1, 'search_placeholder' => __( 'Select a field or enter a custom value or template tag.', 'conditional-form-actions-for-acfe' ), 'allow_custom' => 1, 'conditional_logic' => 0, 'ajax_action' => 'acfe/form/map_field_ajax', 'nonce' => wp_create_nonce( $this->field_key . 'file' ), ], [ 'key' => $this->field_key . 'file_delete', 'label' => __( 'Delete File', 'conditional-form-actions-for-acfe' ), 'name' => 'file_delete', 'type' => 'true_false', 'instructions' => '', 'required' => 0, 'wrapper' => [ 'width' => '', 'class' => '', 'id' => '', ], 'acfe_permissions' => '', 'message' => __( 'Delete once submitted', 'conditional-form-actions-for-acfe' ), 'default_value' => 0, 'ui' => 1, 'ui_on_text' => '', 'ui_off_text' => '', ], ], ]; // Add "Static Files" Repeater. $attachments_tab[] = [ 'key' => $this->field_key . 'files_static', 'label' => __( 'Static Files', 'conditional-form-actions-for-acfe' ), 'name' => 'files_static', 'type' => 'repeater', 'instructions' => '', 'required' => 0, 'conditional_logic' => 0, 'wrapper' => [ 'width' => '', 'class' => '', 'id' => '', ], 'collapsed' => '', 'min' => 0, 'max' => 0, 'layout' => 'table', 'button_label' => __( 'Add File', 'conditional-form-actions-for-acfe' ), 'sub_fields' => [ [ 'key' => $this->field_key . 'file_static', 'label' => __( 'File', 'conditional-form-actions-for-acfe' ), 'name' => 'file_static', 'type' => 'file', 'instructions' => '', 'required' => 0, 'conditional_logic' => 0, 'wrapper' => [ 'width' => '', 'class' => '', 'id' => '', ], 'acfe_permissions' => '', 'return_format' => 'id', ], ], ]; // Get Tab Header. $attachments_tab_header = $this->tab_attachments_header(); // Combine Sub-Fields. $fields = array_merge( $attachments_tab_header, $attachments_tab ); // --< return $fields; } /** * Builds Email data array from mapped Fields. * * @since 0.2.0 * * @param array $form The array of Form data. * @param array $action The array of Action data. * @return array $data The array of results of this Action to save for later use. */ private function form_email_data( $form, $action ) { // Init data array. $data = []; // Set ACFE "context". We want to apply tags. acfe_add_context( [ 'context' => 'display' ] ); // Mapped Email data. $keys = array_keys( $this->mapped_email_fields ); foreach ( $keys as $key ) { acfe_apply_tags( $action['email'][ $key ] ); $data[ $key ] = $action['email'][ $key ]; } // Apply tags to Email content. acfe_apply_tags( $action['email']['content'], [ 'unformat' => 'wysiwyg' ] ); // Reset the ACFE "context". acfe_delete_context( [ 'context' ] ); // Format the Email content. if ( true === $action['email']['html'] ) { // Apply Shortcodes to HTML content. $action['email']['content'] = do_shortcode( $action['email']['content'] ); } else { // Apply "the_content" filters to Rich Text Editor content. $action['email']['content'] = apply_filters( 'acf_the_content', $action['email']['content'] ); } // Now add Email content to data. $data['content'] = $action['email']['content']; // --< return $data; } /** * Builds Attachment data array from mapped Fields. * * @since 0.2.0 * * @param array $form The array of Form data. * @param array $action The array of Action data. * @return array $data The array of Attachment data to save for later use. */ private function form_attachments_data( $form, $action ) { // Init return array. $data = [ 'attachments' => [ 'dynamic' => [], 'static' => [], ], 'delete_after' => [], ]; // Process Dynamic Attachments. if ( ! empty( $action['files']['dynamic'] ) ) { foreach ( $action['files']['dynamic'] as $row ) { // Parse tags but get the data unformatted and raw. $context = [ 'context' => 'save', 'format' => false, 'return' => 'raw', ]; // Find the ID of the File. $file_id = acfe_parse_tags( $row['file'], $context ); // Add to Attachments array. $data['attachments'][] = get_attached_file( $file_id ); // Maybe add to delete array. if ( $row['file_delete'] ) { $data['delete_after'][] = $file_id; } } } // Process Static Attachments. if ( ! empty( $action['files']['static'] ) ) { foreach ( $action['files']['static'] as $row ) { $data['attachments']['static'][] = get_attached_file( $row['file_static'] ); } } // --< return $data; } /** * Builds the arguments array. * * @since 0.2.0 * * @param array $email The array of Email data. * @param array $attachments The array of Attachments data. * @param array $form The array of Form data. * @param array $action The array of Action data. * @return array|bool $args The arguments array, or false on failure. */ private function form_build_args( $email, $attachments, $form, $action ) { // Build "From" and "Reply To" params. $from = $this->form_email_item_build( $email['from_name'], $email['from_email'] ); $reply_to = $this->form_email_item_build( $email['reply_to_name'], $email['reply_to_email'] ); // Maybe use "From" when "Reply To" is empty. if ( empty( $reply_to ) ) { $reply_to = $from; } // Build "To" param. $to = $this->form_email_item_build( $email['to_name'], $email['to_email'] ); // Sanity checks. if ( empty( $from ) || empty( $to ) ) { return false; } // Build attachments array. $attached_files = array_merge( $attachments['attachments']['dynamic'], $attachments['attachments']['static'] ); // Build Email headers. $headers[] = 'From: ' . $from; if ( ! empty( $reply_to ) ) { $headers[] = 'Reply-To: ' . $reply_to; } if ( ! empty( $email['cc'] ) ) { $headers[] = 'Cc: ' . $email['cc']; } if ( ! empty( $email['bcc'] ) ) { $headers[] = 'Bcc: ' . $email['bcc']; } $headers[] = 'Content-Type: text/html'; $headers[] = 'charset=UTF-8'; // Finally, build args array. $args = [ 'from' => $from, 'to' => $to, 'reply_to' => $reply_to, 'cc' => $email['cc'], 'bcc' => $email['bcc'], 'subject' => $email['subject'], 'content' => $email['content'], 'headers' => $headers, 'attachments' => $attached_files, ]; // Get the Form name. $form_name = acf_maybe_get( $form, 'name' ); /** * Allow others to filter the Email arguments. * * Returning false for any of these filters will skip sending the Email. * * @since 0.2.0 * * @param bool $args The array of arguments. * @param array $form The array of Form data. * @param array $action The array of Action data. */ $filter = 'acfe/form/v3/submit/' . $this->name . '/email_args'; $args = apply_filters( $filter, $args, $form, $action ); $args = apply_filters( $filter . '/form=' . $form_name, $args, $form, $action ); if ( ! empty( $action['name'] ) ) { $args = apply_filters( $filter . '/action=' . $action['name'], $args, $form, $action ); } // --< return $args; } /** * Sends the Email given a set of arguments. * * @since 0.2.0 * * @param array $args The array of Email arguments. * @param array $email The array of Email data. * @return array|bool $args The array of Email arguments, or false on failure. */ private function form_email_send( $args, $email ) { // Define rules for checking Email headers. $rules = [ [ 'args_key' => 'from', 'value_old' => $this->form_email_item_build( $email['from_name'], $email['from_email'] ), 'header_key' => 'From:', ], [ 'args_key' => 'reply_to', 'value_old' => $this->form_email_item_build( $email['reply_to_name'], $email['reply_to_email'] ), 'header_key' => 'Reply-To:', ], [ 'args_key' => 'cc', 'value_old' => $email['cc'], 'header_key' => 'Cc:', ], [ 'args_key' => 'bcc', 'value_old' => $email['bcc'], 'header_key' => 'Bcc:', ], ]; // Check if the Email headers have been changed. foreach ( $rules as $rule ) { $check = acf_maybe_get( $args, $rule['args_key'] ); if ( ! empty( $check ) && $check !== $rule['value_old'] ) { foreach ( $args['headers'] as &$header ) { if ( stripos( $header, $rule['header_key'] ) !== 0 ) { continue; } // Repair Email header. $header = $rule['header_key'] . ' ' . $check; break; } } } // Send the Email. $result = wp_mail( $args['to'], $args['subject'], $args['content'], $args['headers'], $args['attachments'] ); if ( false === $result ) { return false; } // --< return $args; } /** * Builds an Email item according to the specification. * * For example "First Last <foo@bar.com>". * * @since 0.2.0 * * @param string $name The name. * @param string $email The email. * @return string $item The built item. */ private function form_email_item_build( $name, $email ) { // Init return. $item = ''; // Parse item from params. if ( ! empty( $name ) && ! empty( $email ) ) { $item = sprintf( '%1$s <%2$s>', $name, $email ); } elseif ( ! empty( $email ) ) { $item = $email; } // --< return $item; } /** * Deletes the Dynamic Attachments. * * @since 0.2.0 * * @param array $attachments The array of Attachments data. */ private function form_attachments_delete( $attachments ) { // Bail if there are none. if ( ! empty( $attachments['delete_after'] ) ) { return; } // Delete all the Dynamic Attachments. foreach ( $attachments['delete_after'] as $file_id ) { wp_delete_attachment( $file_id, true ); } } }