first commit
This commit is contained in:
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Integrations\Actions;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
abstract class Action_Base {
|
||||
|
||||
/**
|
||||
* Validate a payload.
|
||||
*
|
||||
* @param mixed $payload - Payload object instance.
|
||||
*
|
||||
* @throws \ElementorPro\Core\Integrations\Exceptions\Action_Validation_Failed_Exception
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
abstract public function validate( $payload );
|
||||
|
||||
/**
|
||||
* Apply the action.
|
||||
*
|
||||
* @param mixed $payload - Payload object instance.
|
||||
*
|
||||
* @throws \ElementorPro\Core\Integrations\Exceptions\Action_Failed_Exception
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
abstract public function apply( $payload );
|
||||
|
||||
/**
|
||||
* Run the action.
|
||||
*
|
||||
* @param mixed $payload - Payload object instance.
|
||||
*
|
||||
* @throws \ElementorPro\Core\Integrations\Exceptions\Action_Validation_Failed_Exception
|
||||
* @throws \ElementorPro\Core\Integrations\Exceptions\Action_Failed_Exception
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run( $payload ) {
|
||||
$this->validate( $payload );
|
||||
$this->apply( $payload );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Integrations\Actions\Email;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Email_Address {
|
||||
|
||||
/**
|
||||
* Recipient email address.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $address;
|
||||
|
||||
/**
|
||||
* Recipient name.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $name;
|
||||
|
||||
/**
|
||||
* Email_Address constructor.
|
||||
*
|
||||
* @param string $address
|
||||
* @param string $name
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $address, $name ) {
|
||||
$this->address = (string) $address;
|
||||
$this->name = (string) $name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an email to be ready for header (e.g. `Recipient Name <user@email.com>` or `user@email.com`)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function format() {
|
||||
if ( ! empty( $this->name ) ) {
|
||||
return sprintf( '%s <%s>', $this->name, $this->address );
|
||||
}
|
||||
|
||||
return sprintf( '%s', $this->address );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,239 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Integrations\Actions\Email;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Email_Message {
|
||||
|
||||
/**
|
||||
* Email sender.
|
||||
*
|
||||
* @var Email_Address
|
||||
*/
|
||||
public $from;
|
||||
|
||||
/**
|
||||
* Email recipient.
|
||||
*
|
||||
* @var Email_Address
|
||||
*/
|
||||
public $to;
|
||||
|
||||
/**
|
||||
* Email reply to address.
|
||||
*
|
||||
* @var Email_Address[]
|
||||
*/
|
||||
public $reply_to = [];
|
||||
|
||||
/**
|
||||
* Email CC recipient.
|
||||
*
|
||||
* @var Email_Address[]
|
||||
*/
|
||||
public $cc = [];
|
||||
|
||||
/**
|
||||
* Email BCC recipient.
|
||||
*
|
||||
* @var Email_Address[]
|
||||
*/
|
||||
public $bcc = [];
|
||||
|
||||
/**
|
||||
* Email subject.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $subject;
|
||||
|
||||
/**
|
||||
* Email content type.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $content_type;
|
||||
|
||||
/**
|
||||
* Email body.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $body;
|
||||
|
||||
/**
|
||||
* Email attachments.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
public $attachments = [];
|
||||
|
||||
/**
|
||||
* Email_Message constructor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct() {
|
||||
// Set defaults.
|
||||
$this->from( get_bloginfo( 'admin_email' ), get_bloginfo( 'name' ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the email sender.
|
||||
*
|
||||
* @param string $email
|
||||
* @param string|null $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function from( $email, $name = null ) {
|
||||
$this->from = new Email_Address( $email, $name );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the email recipient.
|
||||
*
|
||||
* @param string $email
|
||||
* @param string|null $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function to( $email, $name = null ) {
|
||||
$this->to = new Email_Address( $email, $name );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a reply to.
|
||||
*
|
||||
* @param string $email
|
||||
* @param string|null $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function reply_to( $email, $name = null ) {
|
||||
$this->reply_to[] = new Email_Address( $email, $name );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a CC.
|
||||
*
|
||||
* @param string $email
|
||||
* @param string|null $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function cc( $email, $name = null ) {
|
||||
$this->cc[] = new Email_Address( $email, $name );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a BCC.
|
||||
*
|
||||
* @param string $email
|
||||
* @param string|null $name
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function bcc( $email, $name = null ) {
|
||||
$this->bcc[] = new Email_Address( $email, $name );
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the email subject.
|
||||
*
|
||||
* @param string $subject
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function subject( $subject ) {
|
||||
$this->subject = (string) $subject;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the email content type.
|
||||
*
|
||||
* @param string $content_type
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function content_type( $content_type ) {
|
||||
$this->content_type = (string) $content_type;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the email body using plain text.
|
||||
*
|
||||
* @param string $body
|
||||
* @param string $content_type
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function body( $body, $content_type = 'text/html' ) {
|
||||
$this->body = (string) $body;
|
||||
|
||||
return $this->content_type( $content_type );
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the email body using a view.
|
||||
*
|
||||
* @param string $path - View path,
|
||||
* @param array $data - Data that will be passes to the view.
|
||||
*
|
||||
* @return $this
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function view( $path, $data = [] ) {
|
||||
if ( ! is_file( $path ) ) {
|
||||
throw new \Exception( "`{$path}` is not a valid view." );
|
||||
}
|
||||
|
||||
ob_start();
|
||||
|
||||
// Inspired from Laravel's view mechanism:
|
||||
// [1] https://github.dev/illuminate/filesystem/blob/b179f9ea3b3195d1f4b5ae2aee67e42eac6ceb5e/Filesystem.php#L98
|
||||
// [2] https://github.dev/illuminate/view/blob/6dd315634a44450c5e443fa8735d4a526833fad3/Engines/PhpEngine.php#L48
|
||||
call_user_func( function( $__view_path, $__view_data ) {
|
||||
extract( $__view_data, EXTR_SKIP ); // phpcs:ignore WordPress.PHP.DontExtract.extract_extract
|
||||
|
||||
unset( $__view_data );
|
||||
|
||||
// `$__view_data` keys are available in the file as variables.
|
||||
require $__view_path;
|
||||
}, $path, $data );
|
||||
|
||||
$this->body = ob_get_clean();
|
||||
|
||||
return $this->content_type( 'text/html' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Add an attachment.
|
||||
*
|
||||
* @param string $path - Attachment path on the server.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function attach( $path ) {
|
||||
$this->attachments[] = (string) $path;
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Integrations\Actions\Email;
|
||||
|
||||
use ElementorPro\Core\Integrations\Actions\Action_Base;
|
||||
use ElementorPro\Core\Integrations\Exceptions\Action_Failed_Exception;
|
||||
use ElementorPro\Core\Integrations\Exceptions\Action_Validation_Failed_Exception;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Email extends Action_Base {
|
||||
|
||||
/**
|
||||
* @param Email_Message $payload
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function apply( $payload ) {
|
||||
// Set default headers.
|
||||
$headers = [
|
||||
sprintf( 'Content-Type: %s; charset=UTF-8', $payload->content_type ),
|
||||
sprintf( 'From: %s', $payload->from->format() ),
|
||||
];
|
||||
|
||||
foreach ( $payload->reply_to as $recipient ) {
|
||||
$headers[] = sprintf( 'Reply-To: %s', $recipient->format() );
|
||||
}
|
||||
|
||||
// Set CC headers.
|
||||
$cc_headers = [];
|
||||
|
||||
foreach ( $payload->cc as $recipient ) {
|
||||
$cc_headers[] = sprintf( 'Cc: %s', $recipient->format() );
|
||||
}
|
||||
|
||||
// Send email.
|
||||
$this->send_mail(
|
||||
$payload->to->format(),
|
||||
$payload->subject,
|
||||
$payload->body,
|
||||
implode( PHP_EOL, array_merge( $headers, $cc_headers ) ),
|
||||
$payload->attachments
|
||||
);
|
||||
|
||||
// Send BCC emails.
|
||||
foreach ( $payload->bcc as $bcc ) {
|
||||
$this->send_mail(
|
||||
$bcc->format(),
|
||||
$payload->subject,
|
||||
$payload->body,
|
||||
implode( PHP_EOL, $headers ),
|
||||
$payload->attachments
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @alias `$this->run()`
|
||||
*
|
||||
* @param Email_Message $payload
|
||||
*
|
||||
* @return void
|
||||
*@throws \Exception
|
||||
*
|
||||
*/
|
||||
public function send( Email_Message $payload ) {
|
||||
$this->run( $payload );
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the email message DTO.
|
||||
*
|
||||
* @param Email_Message $payload
|
||||
*
|
||||
* @throws \ElementorPro\Core\Integrations\Exceptions\Action_Validation_Failed_Exception
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function validate( $payload ) {
|
||||
$required_fields = [
|
||||
'from',
|
||||
'to',
|
||||
'subject',
|
||||
'body',
|
||||
'content_type',
|
||||
];
|
||||
|
||||
foreach ( $required_fields as $field ) {
|
||||
if ( empty( $payload->{$field} ) ) {
|
||||
throw new Action_Validation_Failed_Exception(
|
||||
static::class,
|
||||
"`Email_Message::\${$field}` is required."
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls `wp_mail()`. Used for testing.
|
||||
*
|
||||
* @param mixed ...$args
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function send_mail( ...$args ) {
|
||||
add_action( 'wp_mail_failed', [ $this, 'on_wp_mail_error' ] );
|
||||
|
||||
wp_mail( ...$args );
|
||||
|
||||
remove_action( 'wp_mail_failed', [ $this, 'on_wp_mail_error' ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Throw exception on `wp_mail()` error.
|
||||
*
|
||||
* @param \WP_Error $error
|
||||
*
|
||||
* @throws \ElementorPro\Core\Integrations\Exceptions\Action_Failed_Exception
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function on_wp_mail_error( \WP_Error $error ) {
|
||||
throw new Action_Failed_Exception( static::class, '`wp_mail()` cannot send email', $error );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Integrations\Exceptions;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Action_Failed_Exception extends Exception_Base {
|
||||
|
||||
protected function format_message( $message ) {
|
||||
return sprintf(
|
||||
'Action `%s` failed to run: %s',
|
||||
$this->action,
|
||||
$message
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Integrations\Exceptions;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Action_Validation_Failed_Exception extends Exception_Base {
|
||||
|
||||
protected function format_message( $message ) {
|
||||
return sprintf(
|
||||
'Action `%s` failed validation: %s',
|
||||
$this->action,
|
||||
$message
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Integrations\Exceptions;
|
||||
|
||||
use ElementorPro\Plugin;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
abstract class Exception_Base extends \Exception {
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $action;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $meta = [];
|
||||
|
||||
/**
|
||||
* Get a formatted message specific to the current exception type.
|
||||
*
|
||||
* @param string $message
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
abstract protected function format_message( $message );
|
||||
|
||||
/**
|
||||
* Exception_Base constructor.
|
||||
*
|
||||
* @param string $action - Action name that failed (ideally the class name, e.g. Email::class).
|
||||
* @param string $message - Message to show.
|
||||
* @param array $meta - Exception meta data. Used for logging.
|
||||
*
|
||||
*/
|
||||
public function __construct( $action, $message = '', $meta = [] ) {
|
||||
$this->action = $action;
|
||||
$this->meta = $meta;
|
||||
|
||||
$message = $this->format_message( $message );
|
||||
|
||||
parent::__construct( $message );
|
||||
}
|
||||
|
||||
/**
|
||||
* Log the exception to Elementor's log.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function log() {
|
||||
Plugin::elementor()->logger->get_logger()->error( $this->getMessage(), [ 'meta' => $this->meta ] );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the error format.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function __toString() {
|
||||
return sprintf(
|
||||
'%s: %s',
|
||||
__CLASS__,
|
||||
$this->getMessage()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,112 @@
|
||||
<?php
|
||||
|
||||
namespace ElementorPro\Core\Integrations;
|
||||
|
||||
use ElementorPro\Core\Integrations\Actions\Action_Base;
|
||||
use ElementorPro\Core\Integrations\Actions\Email\Email;
|
||||
use ElementorPro\Core\Integrations\Actions\Email\Email_Message;
|
||||
use ElementorPro\Core\Integrations\Exceptions\Action_Failed_Exception;
|
||||
use ElementorPro\Core\Integrations\Exceptions\Action_Validation_Failed_Exception;
|
||||
use ElementorPro\Core\Utils\Registrar;
|
||||
|
||||
if ( ! defined( 'ABSPATH' ) ) {
|
||||
exit; // Exit if accessed directly
|
||||
}
|
||||
|
||||
class Integrations_Manager {
|
||||
|
||||
/**
|
||||
* Registered action types.
|
||||
*
|
||||
* @var Registrar
|
||||
*/
|
||||
protected $actions_registrar;
|
||||
|
||||
/**
|
||||
* Integrations_Manager constructor.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->actions_registrar = new Registrar();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an action instance.
|
||||
*
|
||||
* @shortcut `Registrar->get()`.
|
||||
*
|
||||
* @return \ElementorPro\Core\Integrations\Actions\Action_Base|null
|
||||
*/
|
||||
public function get_action( $id ) {
|
||||
if ( ! $this->is_initialized() ) {
|
||||
$this->init_actions();
|
||||
}
|
||||
|
||||
return $this->actions_registrar->get( $id );
|
||||
}
|
||||
|
||||
/**
|
||||
* Run an action for a selected payload.
|
||||
*
|
||||
* @param array|mixed $payloads - Payloads instances to run the actions on.
|
||||
* @param null|string $id - If `$payloads` is not an array, a custom action ID can be provided.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function run( $payloads, $id = null ) {
|
||||
if ( ! is_array( $payloads ) ) {
|
||||
$payloads = $id ? [ $id => $payloads ] : [ $payloads ];
|
||||
}
|
||||
|
||||
foreach ( $payloads as $key => $payload ) {
|
||||
// Get the action ID for the provided payload type.
|
||||
$action_id = is_numeric( $key ) ? get_class( $payload ) : $key;
|
||||
|
||||
/**
|
||||
* @type Action_Base $action
|
||||
*/
|
||||
$action = $this->get_action( $action_id );
|
||||
|
||||
if ( ! $action ) {
|
||||
throw new \Exception( "{$action_id} doesn't have an associated `Action`." );
|
||||
}
|
||||
|
||||
if ( ! ( $action instanceof Action_Base ) ) {
|
||||
$action_class = get_class( $action );
|
||||
|
||||
throw new \Exception( "{$action_class} is not a valid `Action_Base`." );
|
||||
}
|
||||
|
||||
try {
|
||||
$action->run( $payload );
|
||||
} catch ( Action_Validation_Failed_Exception $e ) {
|
||||
$e->log();
|
||||
} catch ( Action_Failed_Exception $e ) {
|
||||
$e->log();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the manager actions.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function init_actions() {
|
||||
add_action( 'elementor_pro/core/integrations/actions/register', function ( Registrar $actions_registrar ) {
|
||||
$actions_registrar->register( new Email(), Email_Message::class );
|
||||
} );
|
||||
|
||||
do_action( 'elementor_pro/core/integrations/actions/register', $this->actions_registrar );
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if the manager is initialized.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
protected function is_initialized() {
|
||||
return ! ! did_action( 'elementor_pro/core/integrations/actions/register' );
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user