Email_Notifications

Class for an action that sends email notifications.

Description

Source

File: src/modules/actions/email-notifications.php

class Email_Notifications extends Action implements Assets_Submodule_Interface {

	/**
	 * Template tag handler for email notifications.
	 *
	 * @since 1.0.0
	 * @var Template_Tag_Handler
	 */
	protected $template_tag_handler;

	/**
	 * Template tag handler for email address fields.
	 *
	 * @since 1.0.0
	 * @var Template_Tag_Handler
	 */
	protected $template_tag_handler_email_only;

	/**
	 * Template tag handler for complex fields with more than one line.
	 *
	 * @since 1.0.0
	 * @var Template_Tag_Handler
	 */
	protected $template_tag_handler_complex;

	/**
	 * Temporary storage for email from name.
	 *
	 * @since 1.0.0
	 * @var string
	 */
	protected $from_name = '';

	/**
	 * Temporary storage for email from email.
	 *
	 * @since 1.0.0
	 * @var string
	 */
	protected $from_email = '';

	/**
	 * Temporary storage for PHPMailer error object.
	 *
	 * @since 1.0.0
	 * @var WP_Error|null
	 */
	private $phpmailer_error = null;

	/**
	 * Bootstraps the submodule by setting properties.
	 *
	 * @since 1.0.0
	 */
	protected function bootstrap() {
		$this->slug        = 'email_notifications';
		$this->title       = __( 'Email Notifications', 'torro-forms' );
		// $this->description = __( 'Sends one or more email notifications to specific addresses.', 'torro-forms' );

		$this->register_template_tag_handlers();
	}

	/**
	 * Checks whether the access control is enabled for a specific form.
	 *
	 * @since 1.0.0
	 *
	 * @param Form $form Form object to check.
	 * @return bool True if the access control is enabled, false otherwise.
	 */
	public function enabled( $form ) {
		$notifications = $this->get_form_option( $form->id, 'notifications', array() );

		$notifications = array_filter( $notifications, function( $notification ) {
			return ! empty( $notification['to_email'] ) && ! empty( $notification['subject'] ) && ! empty( $notification['message'] );
		});

		if ( ! empty( $notifications ) ) {
			return true;
		}

		return false;
	}

	/**
	 * Handles the action for a specific form submission.
	 *
	 * @since 1.0.0
	 *
	 * @param Submission $submission Submission to handle by the action.
	 * @param Form       $form       Form the submission applies to.
	 * @return bool|WP_Error True on success, error object on failure.
	 */
	public function handle( $submission, $form ) {
		$notifications = $this->get_form_option( $form->id, 'notifications', array() );
		if ( empty( $notifications ) ) {
			return true;
		}

		$dynamic_template_tags = $this->get_dynamic_template_tags( $form, true );

		foreach ( $dynamic_template_tags as $slug => $data ) {
			if ( isset( $data['email_support'] ) ) {
				$email_support = (bool) $data['email_support'];

				unset( $data['email_support'] );

				if ( $email_support ) {
					$this->template_tag_handler_email_only->add_tag( $slug, $data );
				}
			}

			$this->template_tag_handler->add_tag( $slug, $data );
			$this->template_tag_handler_complex->add_tag( $slug, $data );
		}

		$error = new WP_Error();

		add_filter( 'wp_mail_content_type', array( $this, 'override_content_type' ) );
		add_filter( 'wp_mail_from_name', array( $this, 'override_from_name' ) );
		add_filter( 'wp_mail_from', array( $this, 'override_from_email' ) );
		add_action( 'wp_mail_failed', array( $this, 'store_phpmailer_error' ) );

		$email_header_fields = array(
			'reply_email' => 'Reply-To',
			'cc_email'    => 'Cc',
			'bcc_email'   => 'Bcc',
		);

		foreach ( $notifications as $notification ) {
			if ( empty( $notification['to_email'] ) || empty( $notification['subject'] ) || empty( $notification['message'] ) ) {
				continue;
			}

			foreach ( $notification as $key => $value ) {
				switch ( $key ) {
					case 'from_email':
					case 'reply_email':
					case 'to_email':
					case 'cc_email':
					case 'bcc_email':
						$notification[ $key ] = $this->template_tag_handler_email_only->process_content( $value, array( $form, $submission ) );
						break;
					case 'message':
						$notification[ $key ] = $this->template_tag_handler_complex->process_content( $value, array( $form, $submission ) );
						break;
					default:
						$notification[ $key ] = $this->template_tag_handler->process_content( $value, array( $form, $submission ) );
				}
			}

			$notification['message'] = wpautop( $notification['message'] );

			$this->from_name  = $notification['from_name'];
			$this->from_email = $notification['from_email'];

			$headers = array();
			foreach ( $email_header_fields as $field => $header ) {
				if ( ! empty( $notification[ $field ] ) ) {
					$headers[] = $header . ': ' . $notification[ $field ];
				}
			}

			$sent = wp_mail( $notification['to_email'], $notification['subject'], $notification['message'], $headers );
			if ( ! $sent ) {
				/* translators: %s: email address */
				$error_message = sprintf( __( 'Email notification to %s could not be sent.', 'torro-forms' ), $notification['to_email'] );
				if ( $this->phpmailer_error ) {
					/* translators: %s: error message */
					$error_message .= ' ' . sprintf( __( 'Original error message: %s', 'torro-forms' ), $this->phpmailer_error->get_error_message() );

					$this->phpmailer_error = null;
				}

				$error->add( 'email_notification_not_sent', $error_message );
			}
		}

		$this->from_name  = '';
		$this->from_email = '';

		remove_filter( 'wp_mail_content_type', array( $this, 'override_content_type' ) );
		remove_filter( 'wp_mail_from_name', array( $this, 'override_from_name' ) );
		remove_filter( 'wp_mail_from', array( $this, 'override_from_email' ) );
		remove_action( 'wp_mail_failed', array( $this, 'store_phpmailer_error' ) );

		foreach ( $dynamic_template_tags as $slug => $data ) {
			if ( isset( $data['email_support'] ) && $data['email_support'] ) {
				$this->template_tag_handler_email_only->remove_tag( $slug );
			}

			$this->template_tag_handler->remove_tag( $slug );
			$this->template_tag_handler_complex->remove_tag( $slug );
		}

		if ( ! empty( $error->errors ) ) {
			return $error;
		}

		return true;
	}

	/**
	 * Returns the available meta fields for the submodule.
	 *
	 * @since 1.0.0
	 *
	 * @return array Associative array of `$field_slug => $field_args` pairs.
	 */
	public function get_meta_fields() {
		$meta_fields = $this->_get_meta_fields();

		unset( $meta_fields['enabled'] );

		$domain = wp_parse_url( home_url( '/' ), PHP_URL_HOST );
		if ( ! $domain ) {
			// Fall back to a random domain.
			$domain = 'yourwebsite.com';
		}

		$meta_fields['notifications'] = array(
			'type'        => 'group',
			'label'       => __( 'Notifications', 'torro-forms' ),
			// 'description' => __( 'Add email notifications to send.', 'torro-forms' ),
			'repeatable'  => 8,
			'fields'      => array(
				'from_name'   => array(
					'type'                 => 'templatetagtext',
					'label'                => __( 'From Name', 'torro-forms' ),
					'input_classes'        => array( 'regular-text' ),
					'template_tag_handler' => $this->template_tag_handler,
				),
				'from_email'  => array(
					'type'                 => 'templatetagemail',
					'label'                => __( 'From Email', 'torro-forms' ),
					/* translators: %s: email address */
					'description'          => sprintf( __( 'This email address should contain the same domain like your website (e.g. %s).', 'torro-forms' ), 'email@' . $domain ),
					'input_classes'        => array( 'regular-text' ),
					'template_tag_handler' => $this->template_tag_handler_email_only,
				),
				'reply_email' => array(
					'type'                 => 'templatetagemail',
					'label'                => __( 'Reply Email', 'torro-forms' ),
					'input_classes'        => array( 'regular-text' ),
					'template_tag_handler' => $this->template_tag_handler_email_only,
				),
				'to_email'    => array(
					'type'                 => 'templatetagemail',
					'label'                => __( 'To Email', 'torro-forms' ),
					'input_classes'        => array( 'regular-text' ),
					'template_tag_handler' => $this->template_tag_handler_email_only,
				),
				'cc_email'    => array(
					'type'                 => 'templatetagemail',
					'label'                => _x( 'Cc', 'email', 'torro-forms' ),
					'input_classes'        => array( 'regular-text' ),
					'template_tag_handler' => $this->template_tag_handler_email_only,
				),
				'bcc_email'   => array(
					'type'                 => 'templatetagemail',
					'label'                => _x( 'Bcc', 'email', 'torro-forms' ),
					'input_classes'        => array( 'regular-text' ),
					'template_tag_handler' => $this->template_tag_handler_email_only,
				),
				'subject'     => array(
					'type'                 => 'templatetagtext',
					'label'                => __( 'Subject', 'torro-forms' ),
					'input_classes'        => array( 'regular-text' ),
					'template_tag_handler' => $this->template_tag_handler,
				),
				'message'     => array(
					'type'                 => 'templatetagwysiwyg',
					'label'                => __( 'Message', 'torro-forms' ),
					'media_buttons'        => true,
					'template_tag_handler' => $this->template_tag_handler_complex,
				),
			),
		);

		return $meta_fields;
	}

	/**
	 * Registers the template tag handler for email notifications.
	 *
	 * @since 1.0.0
	 */
	protected function register_template_tag_handlers() {
		$tags = array(
			'sitetitle'          => array(
				'group'       => 'global',
				'label'       => __( 'Site Title', 'torro-forms' ),
				'description' => __( 'Inserts the site title.', 'torro-forms' ),
				'callback'    => function() {
					return get_bloginfo( 'name' );
				},
			),
			'sitetagline'        => array(
				'group'       => 'global',
				'label'       => __( 'Site Tagline', 'torro-forms' ),
				'description' => __( 'Inserts the site tagline.', 'torro-forms' ),
				'callback'    => function() {
					return get_bloginfo( 'description' );
				},
			),
			'siteurl'            => array(
				'group'       => 'global',
				'label'       => __( 'Site URL', 'torro-forms' ),
				'description' => __( 'Inserts the site home URL.', 'torro-forms' ),
				'callback'    => function() {
					return home_url( '/' );
				},
			),
			'adminemail'         => array(
				'group'       => 'global',
				'label'       => __( 'Site Admin Email', 'torro-forms' ),
				'description' => __( 'Inserts the site admin email.', 'torro-forms' ),
				'callback'    => function() {
					return get_option( 'admin_email' );
				},
			),
			'userip'             => array(
				'group'       => 'global',
				'label'       => __( 'User IP', 'torro-forms' ),
				'description' => __( 'Inserts the current user IP address.', 'torro-forms' ),
				'callback'    => function() {
					$validated_ip = filter_var( $_SERVER['REMOTE_ADDR'], FILTER_VALIDATE_IP );
					if ( empty( $validated_ip ) ) {
						return '0.0.0.0';
					}
					return $validated_ip;
				},
			),
			'refererurl'         => array(
				'group'       => 'global',
				'label'       => __( 'Referer URL', 'torro-forms' ),
				'description' => __( 'Inserts the current referer URL.', 'torro-forms' ),
				'callback'    => function() {
					return wp_get_referer();
				},
			),
			'formtitle'          => array(
				'group'       => 'form',
				'label'       => __( 'Form Title', 'torro-forms' ),
				'description' => __( 'Inserts the form title.', 'torro-forms' ),
				'callback'    => function( $form ) {
					return $form->title;
				},
			),
			'formurl'            => array(
				'group'       => 'form',
				'label'       => __( 'Form URL', 'torro-forms' ),
				'description' => __( 'Inserts the URL to the form.', 'torro-forms' ),
				'callback'    => function( $form ) {
					return get_permalink( $form->id );
				},
			),
			'formediturl'        => array(
				'group'       => 'form',
				'label'       => __( 'Form Edit URL', 'torro-forms' ),
				'description' => __( 'Inserts the edit URL for the form.', 'torro-forms' ),
				'callback'    => function( $form ) {
					return get_edit_post_link( $form->id );
				},
			),
			'submissionurl'      => array(
				'group'       => 'submission',
				'label'       => __( 'Submission URL', 'torro-forms' ),
				'description' => __( 'Inserts the URL to the submission.', 'torro-forms' ),
				'callback'    => function( $form, $submission ) {
					return add_query_arg( 'torro_submission_id', $submission->id, get_permalink( $form->id ) );
				},
			),
			'submissionediturl' => array(
				'group'       => 'submission',
				'label'       => __( 'Submission Edit URL', 'torro-forms' ),
				'description' => __( 'Inserts the edit URL for the submission.', 'torro-forms' ),
				'callback'    => function( $form, $submission ) {
					return add_query_arg( 'id', $submission->id, torro()->admin_pages()->get( 'edit_submission' )->url );
				},
			),
			'submissiondatetime' => array(
				'group'       => 'submission',
				'label'       => __( 'Submission Date and Time', 'torro-forms' ),
				'description' => __( 'Inserts the submission date and time.', 'torro-forms' ),
				'callback'    => function( $form, $submission ) {
					$date = $submission->format_datetime( get_option( 'date_format' ), false );
					$time = $submission->format_datetime( get_option( 'time_format' ), false );

					/* translators: 1: formatted date, 2: formatted time */
					return sprintf( _x( '%1$s at %2$s', 'concatenating date and time', 'torro-forms' ), $date, $time );
				},
			),
		);

		$complex_tags = array(
			'allelements'        => array(
				'group'       => 'submission',
				'label'       => __( 'All Element Values', 'torro-forms' ),
				'description' => __( 'Inserts all element values from the submission.', 'torro-forms' ),
				'callback'    => function( $form, $submission ) {
					$element_columns = array();
					foreach ( $form->get_elements() as $element ) {
						$element_type = $element->get_element_type();
						if ( ! $element_type ) {
							continue;
						}

						$element_columns[ $element->id ] = array(
							'columns'  => $element_type->get_export_columns( $element ),
							'callback' => function( $values ) use ( $element, $element_type ) {
								return $element_type->format_values_for_export( $values, $element, 'html' );
							},
						);
					}

					$element_values = $submission->get_element_values_data();

					$output = '<table style="width:100%;">';

					foreach ( $element_columns as $element_id => $data ) {
						$values = isset( $element_values[ $element_id ] ) ? $element_values[ $element_id ] : array();

						$column_values = call_user_func( $data['callback'], $values );

						foreach ( $data['columns'] as $slug => $label ) {
							$output .= '<tr>';
							$output .= '<th scope="row">' . esc_html( $label ) . '</th>';
							$output .= '<td>' . esc_html( $column_values[ $slug ] ) . '</td>';
							$output .= '</tr>';
						}
					}

					$output .= '</table>';

					return $output;
				},
			),
		);

		$groups = array(
			'global'     => _x( 'Global', 'template tag group', 'torro-forms' ),
			'form'       => _x( 'Form', 'template tag group', 'torro-forms' ),
			'submission' => _x( 'Submission', 'template tag group', 'torro-forms' ),
		);

		$this->template_tag_handler            = new Template_Tag_Handler( $this->slug, $tags, array( Form::class, Submission::class ), $groups );
		$this->template_tag_handler_email_only = new Template_Tag_Handler( $this->slug . '_email_only', array( 'adminemail' => $tags['adminemail'] ), array( Form::class, Submission::class ), array( 'global' => $groups['global'] ) );
		$this->template_tag_handler_complex    = new Template_Tag_Handler( $this->slug . '_complex', array_merge( $tags, $complex_tags ), array( Form::class, Submission::class ), $groups );

		$this->module->manager()->template_tag_handlers()->register( $this->template_tag_handler );
		$this->module->manager()->template_tag_handlers()->register( $this->template_tag_handler_email_only );
		$this->module->manager()->template_tag_handlers()->register( $this->template_tag_handler_complex );
	}

	/**
	 * Gets all the dynamic template tags for a form, consisting of the form's element value tags.
	 *
	 * @since 1.0.0
	 *
	 * @param Form $form        Form for which to get the dynamic template tags.
	 * @param bool $back_compat Optional. Whether to also include back-compat keys for Torro Forms before 1.0.0-beta.9. Default false.
	 * @return array Dynamic tags as `$slug => $data` pairs.
	 */
	protected function get_dynamic_template_tags( $form, $back_compat = false ) {
		$tags = array();

		foreach ( $form->get_elements() as $element ) {
			$element_type = $element->get_element_type();
			if ( ! $element_type ) {
				continue;
			}

			$tags[ 'value_element_' . $element->id ] = array(
				'group'       => 'submission',
				/* translators: %s: element label */
				'label'       => sprintf( __( 'Value for &#8220;%s&#8221;', 'torro-forms' ), $element->label ),
				/* translators: %s: element label */
				'description' => sprintf( __( 'Inserts the submission value for the element &#8220;%s&#8221;.', 'torro-forms' ), $element->label ),
				'callback'    => function( $form, $submission ) use ( $element, $element_type ) {
					$element_values = $submission->get_element_values_data();
					if ( ! isset( $element_values[ $element->id ] ) ) {
						return '';
					}

					$export_values = $element_type->format_values_for_export( $element_values[ $element->id ], $element, 'html' );
					if ( ! isset( $export_values[ 'element_' . $element->id . '__main' ] ) ) {
						if ( count( $export_values ) !== 1 ) {
							return '';
						}

						return array_pop( $export_values );
					}

					return $export_values[ 'element_' . $element->id . '__main' ];
				},
			);

			// Add email support to text fields with input_type 'email_address'.
			if ( is_a( $element_type, Textfield::class ) ) {
				$settings = $element_type->get_settings( $element );
				if ( ! empty( $settings['input_type'] ) && 'email_address' === $settings['input_type'] ) {
					$tags[ 'value_element_' . $element->id ]['email_support'] = true;
				}
			}

			if ( $back_compat ) {
				$tags[ $element->label . ':' . $element->id ] = $tags[ 'value_element_' . $element->id ];
			}
		}

		return $tags;
	}

	/**
	 * Gets the email content type.
	 *
	 * @since 1.0.0
	 *
	 * @return string Email content type.
	 */
	public function override_content_type() {
		return 'text/html';
	}

	/**
	 * Gets the email from name.
	 *
	 * @since 1.0.0
	 *
	 * @return string Email from name.
	 */
	public function override_from_name() {
		return $this->from_name;
	}

	/**
	 * Gets the email from email.
	 *
	 * @since 1.0.0
	 *
	 * @return string Email from email.
	 */
	public function override_from_email() {
		return $this->from_email;
	}

	/**
	 * Stores an error object as the internal PHPMailer error.
	 *
	 * @since 1.0.0
	 *
	 * @param WP_Error $error Error object.
	 */
	public function store_phpmailer_error( $error ) {
		$this->phpmailer_error = $error;
	}

	/**
	 * Registers all assets the submodule provides.
	 *
	 * @since 1.0.0
	 *
	 * @param Assets $assets The plugin assets instance.
	 */
	public function register_assets( $assets ) {
		$template_tag_template = '<li class="template-tag template-tag-%slug%">';
		$template_tag_template .= '<button type="button" class="template-tag-button" data-tag="%slug%">%label%</button>';
		$template_tag_template .= '</li>';

		$template_tag_group_template = '<li class="template-tag-list-group template-tag-list-group-%slug%">';
		$template_tag_group_template .= '<span>%label%</span>';
		$template_tag_group_template .= '<ul></ul>';
		$template_tag_group_template .= '</li>';

		$assets->register_script( 'admin-email-notifications', 'assets/dist/js/admin-email-notifications.js', array(
			'deps'          => array( 'jquery', 'torro-template-tag-fields', 'torro-admin-form-builder' ),
			'in_footer'     => true,
			'localize_name' => 'torroEmailNotifications',
			'localize_data' => array(
				'templateTagGroupTemplate' => $template_tag_group_template,
				'templateTagTemplate'      => $template_tag_template,
				'templateTagSlug'          => 'value_element_%element_id%',
				'templateTagGroup'         => 'submission',
				'templateTagGroupLabel'    => _x( 'Submission', 'template tag group', 'torro-forms' ),
				/* translators: %s: element label */
				'templateTagLabel'         => sprintf( __( 'Value for &#8220;%s&#8221;', 'torro-forms' ), '%element_label%' ),
				/* translators: %s: element label */
				'templateTagDescription'   => sprintf( __( 'Inserts the submission value for the element &#8220;%s&#8221;.', 'torro-forms' ), '%element_label%' ),
			),
		) );
	}

	/**
	 * Enqueues scripts and stylesheets on the form editing screen.
	 *
	 * @since 1.0.0
	 *
	 * @param Assets $assets The plugin assets instance.
	 */
	public function enqueue_form_builder_assets( $assets ) {
		$assets->enqueue_script( 'admin-email-notifications' );
	}
}

Changelog

Changelog
Version Description
1.0.0 Introduced.

Methods