<?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'; /** * Form Action Name. * * @since 0.2.0 * @access public * @var string */ public $action_name = 'email_cfafa'; /** * Field Key Prefix. * * @since 0.2.0 * @access public * @var string */ public $field_key = 'field_cfafa_email_'; /** * Field Name Prefix. * * @since 0.2.0 * @access public * @var string */ public $field_name = 'cfafa_email_'; /** * 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' ); // Conditional Code for this Form Action. $this->conditional_code = 'email_conditional'; // 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' => '', 'html' => false, ]; // Declare mapped Fields for this Form Action. $keys = array_keys( $this->mapped_email_fields ); foreach ( $keys as $key ) { $this->item[ $this->field_name . 'map_' . $key ] = ''; } // Declare static Fields for this Form Action. $this->item[ $this->field_name . 'content' ] = ''; $this->item[ $this->field_name . 'files' ] = []; $this->item[ $this->field_name . 'files_static' ] = []; // Declare Conditional Field for this Form Action. $this->item[ $this->field_name . 'map_' . $this->conditional_code ] = ''; // 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. $item = $this->field_name . 'map_' . $this->conditional_code; if ( acf_maybe_get( $action, $item ) ) { $save[ $item ] = $action[ $item ]; } // Email data. $keys = array_keys( $this->mapped_email_fields ); foreach ( $keys as $key ) { $item = $this->field_name . 'map_' . $key; if ( acf_maybe_get( $action, $item ) ) { $save[ $item ] = $action[ $item ]; } } // Get Content Type from Content Group. $group = $action[ $this->field_name . 'content_group' ]; if ( 'editor' === $group[ $this->field_name . 'content_type' ] ) { $save[ $this->field_name . 'content' ] = $group[ $this->field_name . 'content_editor' ]; } elseif ( 'html' === $group[ $this->field_name . 'content_type' ] ) { $save[ $this->field_name . 'content' ] = $group[ $this->field_name . 'content_html' ]; $save['html'] = true; } // Dynamic Files. $item = $this->field_name . 'files'; $action[ $item ] = acf_get_array( $action[ $item ] ); foreach ( $action[ $item ] as $row ) { $save[ $item ][] = $row; } // Static Files. $item = $this->field_name . 'files_static'; $action[ $item ] = acf_get_array( $action[ $item ] ); foreach ( $action[ $item ] as $row ) { $save[ $item ][] = $row[ $this->field_name . 'file_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 ) { // Populate Content Editor. $value = $action[ $this->field_name . 'content' ]; $group = $this->field_name . 'content_group'; if ( true === $action['html'] ) { $action[ $group ][ $this->field_name . 'content_type' ] = 'html'; $action[ $group ][ $this->field_name . 'content_html' ] = $value; } else { $action[ $group ][ $this->field_name . 'content_type' ] = 'editor'; $action[ $group ][ $this->field_name . 'content_editor' ] = $value; } // Populate Dynamic Files. $item = $this->field_name . 'files'; $files = $action[ $item ]; $action[ $item ] = []; foreach ( $files as $row ) { $action[ $item ][] = [ $this->field_name . 'file' => $row[ $this->field_name . 'file' ], $this->field_name . 'file_delete' => $row[ $this->field_name . 'file_delete' ], ]; } // Populate Static Files. $item = $this->field_name . 'files_static'; $files = $action[ $item ]; $action[ $item ] = []; foreach ( $files as $row ) { $action[ $item ][] = [ $this->field_name . 'file_static' => $row ]; } // --< 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 ); } } // Save the results of this Action for later use. $this->make_action_save( $action, $email ); } /** * Defines additional Fields for the "Action" Tab. * * @since 0.2.0 * * @return array $fields The array of Fields for this section. */ public function tab_action_append() { // Init Fields. $fields = []; // Add Conditional Field. $label = __( 'Conditional On', 'conditional-form-actions-for-acfe' ); $conditional = $this->mapping_field_get( $this->conditional_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.2.0 * * @return array $fields The array of Fields for this section. */ public 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. */ public 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' => $this->field_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' => $this->field_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' => $this->field_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' => $this->field_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. */ public function tab_attachments_add() { // Init tab array. $attachments_tab = []; // Add "Dynamic Files" Repeater. $attachments_tab[] = [ 'key' => $this->field_key . 'files', 'label' => __( 'Dynamic Files', 'conditional-form-actions-for-acfe' ), 'name' => $this->field_name . 'files', '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' => $this->field_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', ], [ 'key' => $this->field_key . 'file_delete', 'label' => __( 'Delete File', 'conditional-form-actions-for-acfe' ), 'name' => $this->field_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' => $this->field_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' => $this->field_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. */ public 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 ) { $index = $this->field_name . 'map_' . $key; acfe_apply_tags( $action[ $index ] ); $data[ $key ] = $action[ $index ]; } // Apply tags to Email content. acfe_apply_tags( $action[ $this->field_name . 'content' ], [ 'unformat' => 'wysiwyg' ] ); // Reset the ACFE "context". acfe_delete_context( [ 'context' ] ); // Format the Email content. if ( true === $action['html'] ) { // Apply Shortcodes to HTML content. $action[ $this->field_name . 'content' ] = do_shortcode( $action[ $this->field_name . 'content' ] ); } else { // Apply "the_content" filters to Rich Text Editor content. $action[ $this->field_name . 'content' ] = apply_filters( 'acf_the_content', $action[ $this->field_name . 'content' ] ); } // Now add Email content to data. $data['content'] = $action[ $this->field_name . 'content' ]; // Also save conditional. $data['conditional'] = $action['conditional']; // --< 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. */ public function form_attachments_data( $form, $action ) { // Init return array. $data = [ 'attachments' => [], 'delete' => [], ]; // Process Dynamic Attachments. foreach ( $action[ $this->field_name . 'files' ] 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_field_key = $row[ $this->field_name . 'file' ]; $file_id = acfe_parse_tags( $file_field_key, $context ); // Add to Attachments array. $data['attachments'][] = get_attached_file( $file_id ); // Maybe add to delete array. $file_delete = $row[ $this->field_name . 'file_delete' ]; if ( $file_delete ) { $data['delete'][] = $file_id; } } // Process Static Attachments. foreach ( $action[ $this->field_name . 'files_static' ] as $file ) { $data['attachments'][] = get_attached_file( $file ); } // --< 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. */ public 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 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' => $attachments['attachments'], ]; // 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->action_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. */ public 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'] ); // Bail on failure. 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. */ public 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. */ public function form_attachments_delete( $attachments ) { // Bail if there are none. if ( ! empty( $attachments['delete'] ) ) { return; } // Delete all the Dynamic Attachments. foreach ( $attachments['delete'] as $file_id ) { wp_delete_attachment( $file_id, true ); } } }