Version 5.0.0 beta 1
This commit is contained in:
1 parent
25ddeb65d6
commit
15c7beabc5
6736 files changed
+627902
-497943
No files matched your search
@@ -11,29 +11,34 @@
|
||||
namespace IPS\Email\Outgoing;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use IPS\Email;
|
||||
use function defined;
|
||||
use function file_put_contents;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug Email Class
|
||||
*/
|
||||
class _Debug extends \IPS\Email
|
||||
class Debug extends Email
|
||||
{
|
||||
/**
|
||||
* @brief Debug path to write the email files
|
||||
*/
|
||||
protected $debugPath = '';
|
||||
protected string $debugPath = '';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $debugPath Debug path
|
||||
* @param string $debugPath Debug path
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $debugPath )
|
||||
public function __construct( string $debugPath )
|
||||
{
|
||||
$this->debugPath = $debugPath;
|
||||
}
|
||||
@@ -42,43 +47,27 @@ class _Debug extends \IPS\Email
|
||||
* Send the email
|
||||
*
|
||||
* @param mixed $to The member or email address, or array of members or email addresses, to send to
|
||||
* @param mixed $cc Addresses to CC (can also be email, member or array of either)
|
||||
* @param mixed $bcc Addresses to BCC (can also be email, member or array of either)
|
||||
* @param mixed $fromEmail The email address to send from. If NULL, default setting is used
|
||||
* @param mixed $fromName The name the email should appear from. If NULL, default setting is used
|
||||
* @param array $additionalHeaders The name the email should appear from. If NULL, default setting is used
|
||||
* @param mixed $cc Addresses to CC (can also be email, member or array of either)
|
||||
* @param mixed $bcc Addresses to BCC (can also be email, member or array of either)
|
||||
* @param mixed $fromEmail The email address to send from. If NULL, default setting is used
|
||||
* @param mixed $fromName The name the email should appear from. If NULL, default setting is used
|
||||
* @param array $additionalHeaders The name the email should appear from. If NULL, default setting is used
|
||||
* @return void
|
||||
* @throws \IPS\Email\Outgoing\Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function _send( $to, $cc=array(), $bcc=array(), $fromEmail = NULL, $fromName = NULL, $additionalHeaders = array() )
|
||||
public function _send( mixed $to, mixed $cc=array(), mixed $bcc=array(), mixed $fromEmail = NULL, mixed $fromName = NULL, array $additionalHeaders = array() ) : void
|
||||
{
|
||||
if( !is_dir( $this->debugPath ) )
|
||||
{
|
||||
throw new \IPS\Email\Outgoing\Exception( 'no_path_email_debug', 1, NULL, array( $this->debugPath ) );
|
||||
throw new Exception( 'no_path_email_debug', 1, NULL, array( $this->debugPath ) );
|
||||
}
|
||||
|
||||
$fullEmailContents = $this->compileFullEmail( $to, $cc, $bcc, $fromEmail, $fromName, $additionalHeaders );
|
||||
|
||||
$filename = date("M-j-Y") . '-' . microtime() . '-' . urlencode( mb_substr( $this->_parseRecipients( $to, TRUE ), 0, 200 ) ) . ".eml";
|
||||
if( !@\file_put_contents( rtrim( $this->debugPath, '/' ) . '/' . $filename, $fullEmailContents ) )
|
||||
if( !@file_put_contents( rtrim( $this->debugPath, '/' ) . '/' . $filename, $fullEmailContents ) )
|
||||
{
|
||||
throw new \IPS\Email\Outgoing\Exception( 'no_write_email_debug', 2, NULL, array( rtrim( $this->debugPath, '/' ) . '/' . $filename ) );
|
||||
throw new Exception( 'no_write_email_debug', 2, NULL, array( rtrim( $this->debugPath, '/' ) . '/' . $filename ) );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this email class usable?
|
||||
*
|
||||
* @param string $type Email Type
|
||||
* @return bool
|
||||
*/
|
||||
public static function isUsable( string $type ): bool
|
||||
{
|
||||
if( \IPS\EMAIL_DEBUG_PATH )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -11,38 +11,43 @@
|
||||
namespace IPS\Email\Outgoing;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use IPS\Member;
|
||||
use RuntimeException;
|
||||
use function defined;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception class for email errors
|
||||
*/
|
||||
class _Exception extends \RuntimeException
|
||||
class Exception extends RuntimeException
|
||||
{
|
||||
/**
|
||||
* @brief Extra details for log
|
||||
*/
|
||||
public $extraDetails = array();
|
||||
public $messageKey = NULL;
|
||||
public ?array $extraDetails = array();
|
||||
public ?string $messageKey = NULL;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $message Error message
|
||||
* @param int $code Error Code
|
||||
* @param \Exception|NULL $previous Previous Exception
|
||||
* @param array|NULL $extra Extra details for log
|
||||
* @return void
|
||||
* @param string|null $message Error message
|
||||
* @param int $code Error Code
|
||||
* @param Exception|NULL $previous Previous Exception
|
||||
* @param array|null $extra Extra details for log
|
||||
*/
|
||||
public function __construct( $message = null, $code = 0, $previous = null, $extra=NULL )
|
||||
public function __construct( string $message = null, int $code = 0, \Exception|null $previous = null, array $extra=NULL )
|
||||
{
|
||||
/* Store these for the extraLogData() method */
|
||||
$this->extraDetails = $extra;
|
||||
$this->messageKey = $message;
|
||||
$message = \IPS\Member::loggedIn()->language()->addToStack( $message, FALSE, array( 'sprintf' => $extra ) );
|
||||
|
||||
$message = Member::loggedIn()->language()->addToStack( $message, FALSE, array( 'sprintf' => $extra ) );
|
||||
|
||||
return parent::__construct( $message, $code, $previous );
|
||||
}
|
||||
|
||||
@@ -10,38 +10,43 @@
|
||||
|
||||
namespace IPS\Email\Outgoing;
|
||||
|
||||
use IPS\Helpers\Form\Text;
|
||||
use IPS\Settings;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use IPS\Email;
|
||||
use IPS\IPS;
|
||||
use IPS\Settings;
|
||||
use function defined;
|
||||
use function function_exists;
|
||||
use function in_array;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* PHP Email Class
|
||||
*/
|
||||
class _PHP extends \IPS\Email
|
||||
class Php extends Email
|
||||
{
|
||||
/**
|
||||
* Send the email
|
||||
*
|
||||
* @param mixed $to The member or email address, or array of members or email addresses, to send to
|
||||
* @param mixed $cc Addresses to CC (can also be email, member or array of either)
|
||||
* @param mixed $bcc Addresses to BCC (can also be email, member or array of either)
|
||||
* @param mixed $fromEmail The email address to send from. If NULL, default setting is used
|
||||
* @param mixed $fromName The name the email should appear from. If NULL, default setting is used
|
||||
* @param array $additionalHeaders The name the email should appear from. If NULL, default setting is used
|
||||
* @param mixed $cc Addresses to CC (can also be email, member or array of either)
|
||||
* @param mixed $bcc Addresses to BCC (can also be email, member or array of either)
|
||||
* @param mixed $fromEmail The email address to send from. If NULL, default setting is used
|
||||
* @param mixed $fromName The name the email should appear from. If NULL, default setting is used
|
||||
* @param array $additionalHeaders The name the email should appear from. If NULL, default setting is used
|
||||
* @return void
|
||||
* @throws \IPS\Email\Outgoing\Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function _send( $to, $cc=array(), $bcc=array(), $fromEmail = NULL, $fromName = NULL, $additionalHeaders = array() )
|
||||
public function _send( mixed $to, mixed $cc=array(), mixed $bcc=array(), mixed $fromEmail = NULL, mixed $fromName = NULL, array $additionalHeaders = array() ) : void
|
||||
{
|
||||
if( !\function_exists( 'mail' ) )
|
||||
if( !function_exists( 'mail' ) )
|
||||
{
|
||||
throw new \IPS\Email\Outgoing\Exception( 'email_test_mailfunction_disabled' );
|
||||
throw new Exception( 'email_test_mailfunction_disabled' );
|
||||
}
|
||||
|
||||
$boundary = "--==_mimepart_" . md5( mt_rand() );
|
||||
@@ -50,7 +55,7 @@ class _PHP extends \IPS\Email
|
||||
$headers = array();
|
||||
foreach( $this->_compileHeaders( $subject, $to, $cc, $bcc, $fromEmail, $fromName, $additionalHeaders, $boundary ) as $k => $v )
|
||||
{
|
||||
if ( !\in_array( $k, array( 'To', 'Subject' ) ) )
|
||||
if ( !in_array( $k, array( 'To', 'Subject' ) ) )
|
||||
{
|
||||
$headers[] = "{$k}: {$v}";
|
||||
}
|
||||
@@ -60,50 +65,20 @@ class _PHP extends \IPS\Email
|
||||
{
|
||||
if ( !mail( $this->_parseRecipients( $to, TRUE ), static::encodeHeader( $subject ), $this->_compileMessage( static::_getMemberFromRecipients( $to ), $boundary, "\r\n", 68 ), implode( "\r\n", $headers ), Settings::i()->php_mail_extra ) )
|
||||
{
|
||||
if ( $error = \IPS\IPS::$lastError )
|
||||
if ( $error = IPS::$lastError )
|
||||
{
|
||||
throw new \IPS\Email\Outgoing\Exception( $error->getMessage(), $error->getCode() );
|
||||
throw new Exception( $error->getMessage(), $error->getCode() );
|
||||
}
|
||||
else
|
||||
{
|
||||
/* If $error is null, mail() is probably disabled */
|
||||
throw new \IPS\Email\Outgoing\Exception( 'email_test_mailfunction_disabled' );
|
||||
throw new Exception( 'email_test_mailfunction_disabled' );
|
||||
}
|
||||
}
|
||||
}
|
||||
catch ( \Exception $e )
|
||||
catch ( Exception $e )
|
||||
{
|
||||
throw new \IPS\Email\Outgoing\Exception( $e->getMessage() );
|
||||
throw new Exception( $e->getMessage() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this email class usable?
|
||||
*
|
||||
* @param string $type Email Type
|
||||
* @return bool
|
||||
*/
|
||||
public static function isUsable( string $type ): bool
|
||||
{
|
||||
if( !\IPS\CIC )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form fields for email handler
|
||||
* These will automatically toggle on when the email handler is selected to be used
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function form(): array
|
||||
{
|
||||
$form = [];
|
||||
$form['php_mail_extra'] = new Text( 'php_mail_extra', Settings::i()->php_mail_extra, FALSE, [], NULL, NULL, NULL, 'php_mail_extra' );
|
||||
|
||||
return $form;
|
||||
}
|
||||
}
|
||||
@@ -1,347 +0,0 @@
|
||||
<?php
|
||||
/**
|
||||
* @brief Postmark Email Class
|
||||
* @author <a href='https://www.invisioncommunity.com'>Invision Power Services, Inc.</a>
|
||||
* @copyright (c) Invision Power Services, Inc.
|
||||
* @license https://www.invisioncommunity.com/legal/standards/
|
||||
* @package Invision Community
|
||||
* @since 21 November 2024
|
||||
*/
|
||||
|
||||
namespace IPS\Email\Outgoing;
|
||||
|
||||
use Cassandra\Set;
|
||||
use IPS\core\Advertisement;
|
||||
use IPS\Db;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Lang;
|
||||
use IPS\Member;
|
||||
use IPS\Settings;
|
||||
use const IPS\LONG_REQUEST_TIMEOUT;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Postmark Email Class
|
||||
*/
|
||||
class _Postmark extends \IPS\Email
|
||||
{
|
||||
/* !Configuration */
|
||||
|
||||
/**
|
||||
* @brief The number of emails that can be sent in one "go"
|
||||
*/
|
||||
const MAX_EMAILS_PER_GO = 500; // Postmark can accept 500 messages per batch call
|
||||
|
||||
/**
|
||||
* @brief API Key
|
||||
*/
|
||||
protected string $apiKey;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $apiKey API Key
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( string $apiKey )
|
||||
{
|
||||
$this->apiKey = $apiKey;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send the email
|
||||
*
|
||||
* @param mixed $to The member or email address, or array of members or email addresses, to send to
|
||||
* @param mixed $cc Addresses to CC (can also be email, member or array of either)
|
||||
* @param mixed $bcc Addresses to BCC (can also be email, member or array of either)
|
||||
* @param mixed $fromEmail The email address to send from. If NULL, default setting is used
|
||||
* @param mixed $fromName The name the email should appear from. If NULL, default setting is used
|
||||
* @param array $additionalHeaders The name the email should appear from. If NULL, default setting is used
|
||||
* @return void
|
||||
* @throws \IPS\Email\Outgoing\Exception
|
||||
*/
|
||||
public function _send( $to, $cc=array(), $bcc=array(), $fromEmail = NULL, $fromName = NULL, $additionalHeaders = array() )
|
||||
{
|
||||
$emailData = $this->_constructEmailRequest( $to, $cc, $bcc, $fromEmail, $fromName, $additionalHeaders );
|
||||
|
||||
/* Send */
|
||||
$response = $this->api( 'email', $emailData );
|
||||
if ( !empty( $response['ErrorCode'] ) )
|
||||
{
|
||||
throw new \IPS\Email\Outgoing\Exception( $response['Message'], $response['ErrorCode'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this email class usable?
|
||||
*
|
||||
* @param string $type Email Type
|
||||
* @return bool
|
||||
*/
|
||||
public static function isUsable( string $type ): bool
|
||||
{
|
||||
if( ( new \IPS\core\extensions\core\CommunityEnhancements\Postmark() )->enabled )
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge and Send
|
||||
*
|
||||
* @param array $recipients Array where the keys are the email addresses to send to and the values are an array of variables to replace
|
||||
* @param mixed $fromEmail The email address to send from. If NULL, default setting is used. NOTE: This should always be a site-controlled domin. Some services like Sparkpost require the domain to be validated.
|
||||
* @param mixed $fromName The name the email should appear from. If NULL, default setting is used
|
||||
* @param array $additionalHeaders Additional headers to send. Merge tags can be used like in content.
|
||||
* @param Lang|NULL $language The language the email content should be in
|
||||
* @return int Number of successful sends
|
||||
*/
|
||||
public function mergeAndSend( $recipients, $fromEmail = NULL, $fromName = NULL, $additionalHeaders = [], Lang $language = NULL )
|
||||
{
|
||||
/* Get the current locale, and then set the language's locale so datetime formatting in templates is correct for this language */
|
||||
$currentLocale = setlocale( LC_ALL, '0' );
|
||||
$language->setLocale();
|
||||
|
||||
$emailData = $emailDataForLog = [];
|
||||
|
||||
/* We need to know before we start if tracking has been completed or not for the content already */
|
||||
$trackingCompleted = $this->trackingCompleted;
|
||||
|
||||
foreach ( $recipients as $address => $vars )
|
||||
{
|
||||
/* Before compiling our content, reset the "tracking completed flag", otherwise if it hasn't been done yet, the flag is set during the first loop and never reset (so tracking isn't performed) for subsequent loops */
|
||||
$this->trackingCompleted = $trackingCompleted;
|
||||
|
||||
$e = $this->_constructEmailRequest( to: $address, fromEmail: $fromEmail, fromName: $fromName, language: $language );
|
||||
|
||||
foreach ( $vars as $k => $v )
|
||||
{
|
||||
$language->parseEmail( $v );
|
||||
|
||||
$e['HtmlBody'] = str_replace( "*|{$k}|*", htmlspecialchars( $v, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', TRUE ), $e['HtmlBody'] );
|
||||
$e['TextBody'] = str_replace( "*|{$k}|*", $v, $e['TextBody'] );
|
||||
$e['Subject'] = str_replace( "*|{$k}|*", $v, $e['Subject'] );
|
||||
|
||||
foreach ( $e['Headers'] as $i => $header )
|
||||
{
|
||||
$e['Headers'][ $i ]['Value'] = str_replace( "*|{$k}|*", $v, $header['Value'] );
|
||||
}
|
||||
}
|
||||
|
||||
$emailDataForLog[ $address ] = [
|
||||
'Subject' => $e['Subject'],
|
||||
'HtmlBody' => $e['HtmlBody'],
|
||||
];
|
||||
$emailData[] = $e;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
/* Send */
|
||||
$response = $this->api( 'email/batch', $emailData );
|
||||
}
|
||||
catch( \JsonException | \IPS\Email\Outgoing\Exception $e )
|
||||
{
|
||||
$first = array_shift( $emailData );
|
||||
Db::i()->insert( 'core_mail_error_logs', [
|
||||
'mlog_date' => time(),
|
||||
'mlog_to' => json_encode( array_keys( $emailDataForLog ) ),
|
||||
'mlog_from' => $fromEmail ?: Settings::i()->email_out,
|
||||
'mlog_subject' => $first['Subject'],
|
||||
'mlog_content' => $first['HtmlBody'],
|
||||
'mlog_resend_data' => json_encode( [ 'type' => $this->type ] ),
|
||||
'mlog_msg' => json_encode( [ 'message' => $e->getMessage() ] ),
|
||||
'mlog_smtp_log' => NULL
|
||||
] );
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
$errorCount = 0;
|
||||
foreach( $response as $k => $result )
|
||||
{
|
||||
if( !empty( $result['ErrorCode'] ) )
|
||||
{
|
||||
$errorCount++;
|
||||
Db::i()->insert( 'core_mail_error_logs', array(
|
||||
'mlog_date' => time(),
|
||||
'mlog_to' => $emailData[ $k ]['To'],
|
||||
'mlog_from' => $fromEmail ?: Settings::i()->email_out,
|
||||
'mlog_subject' => $emailData[ $k ]['Subject'],
|
||||
'mlog_content' => $emailData[ $k ]['HtmlBody'],
|
||||
'mlog_resend_data' => json_encode( [ 'type' => $this->type ] ),
|
||||
'mlog_msg' => json_encode( [ 'code' => $result['ErrorCode'], 'message' => $result['Message'] ] ),
|
||||
'mlog_smtp_log' => NULL
|
||||
) );
|
||||
}
|
||||
}
|
||||
|
||||
$successCount = ( \count( $recipients ) - $errorCount );
|
||||
|
||||
/* Update ad impression count */
|
||||
Advertisement::updateEmailImpressions( $successCount );
|
||||
|
||||
/* Now restore the locale we started with */
|
||||
Lang::restoreLocale( $currentLocale );
|
||||
|
||||
return $successCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construct email array for Postmar
|
||||
*
|
||||
* @param Member|string|array $to Recipient addresses
|
||||
* @param string|array $cc CC addresses
|
||||
* @param string|array $bcc BCC Addresses
|
||||
* @param string|null $fromEmail From Email Address
|
||||
* @param string|null $fromName From Name
|
||||
* @param array|null $additionalHeaders Additional headers
|
||||
* @param Lang|NULL $language
|
||||
* @return array
|
||||
*/
|
||||
protected function _constructEmailRequest( Member|string|array $to, string|array $cc=[], string|array $bcc=[], ?string $fromEmail=NULL, ?string $fromName=NULL, ?array $additionalHeaders=array(), Lang $language=NULL ): array
|
||||
{
|
||||
$fromName = $fromName ?: Settings::i()->board_name;
|
||||
$fromEmail = $fromEmail ?: Settings::i()->email_out;
|
||||
|
||||
/* Add the recipients */
|
||||
$sendTo = $sendCc = $sendBcc = [];
|
||||
foreach ( [ 'to', 'cc', 'bcc' ] as $type )
|
||||
{
|
||||
$sendVar = 'send' . ucfirst( $type );
|
||||
if ( \is_array( $$type ) )
|
||||
{
|
||||
foreach ( $$type as $recipient )
|
||||
{
|
||||
if ( $recipient instanceof Member )
|
||||
{
|
||||
$$sendVar[] = $recipient->email;
|
||||
}
|
||||
else
|
||||
{
|
||||
$$sendVar[] = $recipient;
|
||||
}
|
||||
}
|
||||
}
|
||||
elseif ( $$type )
|
||||
{
|
||||
$recipient = $$type;
|
||||
if ( $recipient instanceof Member )
|
||||
{
|
||||
$$sendVar[] = $recipient->email;
|
||||
}
|
||||
else
|
||||
{
|
||||
$$sendVar[] = $recipient;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$email = [
|
||||
'From' => "{$fromName} <{$fromEmail}>",
|
||||
'To' => implode( ',', $sendTo ),
|
||||
'Cc' => implode( ',', $sendCc ),
|
||||
'Bcc' => implode( ',', $sendBcc ),
|
||||
'Subject' => $this->compileSubject( static::_getMemberFromRecipients( $to ), $language ),
|
||||
'HtmlBody' => $this->compileContent( 'html', static::_getMemberFromRecipients( $to ), $language ),
|
||||
'TextBody' => $this->compileContent( 'plaintext', static::_getMemberFromRecipients( $to ), $language ),
|
||||
'Headers' => [],
|
||||
'MessageStream' => $this->getMessageStreamId( $this->type ),
|
||||
'TrackOpens' => Settings::i()->postmark_track_opens,
|
||||
'TrackLinks' => Settings::i()->postmark_track_opens ? 'HtmlAndText' : 'None'
|
||||
];
|
||||
|
||||
if( $additionalHeaders )
|
||||
{
|
||||
foreach( $additionalHeaders as $id => $header )
|
||||
{
|
||||
$email['Headers'][] = [
|
||||
'Name' => $id,
|
||||
'Value' => $header,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Make API call
|
||||
*
|
||||
* @param string $method Method
|
||||
* @param array $args Arguments
|
||||
* @throws \IPS\Email\Outgoing\Exception Indicates an invalid JSON response or HTTP error
|
||||
* @return array|null
|
||||
*/
|
||||
public function api( $method, $args=NULL )
|
||||
{
|
||||
$request = Url::external( 'https://api.postmarkapp.com/' . $method )
|
||||
->request( LONG_REQUEST_TIMEOUT )
|
||||
->setHeaders( [
|
||||
'Accept' => 'application/json',
|
||||
'Content-Type' => 'application/json',
|
||||
'X-Postmark-Server-Token' => $this->apiKey
|
||||
] );
|
||||
|
||||
try
|
||||
{
|
||||
if ( $args )
|
||||
{
|
||||
$response = $request->post( json_encode( $args ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
$response = $request->get();
|
||||
}
|
||||
|
||||
if ( $response->content )
|
||||
{
|
||||
$response = $response->decodeJson();
|
||||
}
|
||||
else
|
||||
{
|
||||
$response = null;
|
||||
}
|
||||
|
||||
return $response;
|
||||
}
|
||||
catch ( \RuntimeException | \IPS\Http\Request\Exception $e )
|
||||
{
|
||||
throw new \IPS\Email\Outgoing\Exception( $e->getMessage(), $e->getCode() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array|null Unpacked message stream config
|
||||
*/
|
||||
protected static null|array $messageStreams = null;
|
||||
|
||||
/**
|
||||
* Get Message Stream ID based on config
|
||||
*
|
||||
* @param string $type TYPE_* constant
|
||||
* @return string
|
||||
*/
|
||||
public function getMessageStreamId( string $type ): string
|
||||
{
|
||||
if( self::$messageStreams === null )
|
||||
{
|
||||
$setting = json_decode( Settings::i()->postmark_streams, TRUE );
|
||||
self::$messageStreams = [
|
||||
static::TYPE_TRANSACTIONAL => $setting['transactional'],
|
||||
static::TYPE_LIST => $setting['bulk'],
|
||||
static::TYPE_BULK => $setting['bulk']
|
||||
];
|
||||
}
|
||||
|
||||
return self::$messageStreams[ $type ];
|
||||
}
|
||||
}
|
||||
@@ -10,19 +10,32 @@
|
||||
|
||||
namespace IPS\Email\Outgoing;
|
||||
|
||||
use IPS\Settings;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use IPS\core\Advertisement;
|
||||
use IPS\Db;
|
||||
use IPS\Email;
|
||||
use IPS\Http\Request\Exception;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Lang;
|
||||
use IPS\Member;
|
||||
use IPS\Settings;
|
||||
use RuntimeException;
|
||||
use function count;
|
||||
use function defined;
|
||||
use function is_array;
|
||||
use const IPS\LONG_REQUEST_TIMEOUT;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* SendGrid Email Class
|
||||
*/
|
||||
class _SendGrid extends \IPS\Email
|
||||
class SendGrid extends Email
|
||||
{
|
||||
/* !Configuration */
|
||||
|
||||
@@ -34,15 +47,15 @@ class _SendGrid extends \IPS\Email
|
||||
/**
|
||||
* @brief API Key
|
||||
*/
|
||||
protected $apiKey;
|
||||
protected string $apiKey;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $apiKey API Key
|
||||
* @param string $apiKey API Key
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $apiKey )
|
||||
public function __construct( string $apiKey )
|
||||
{
|
||||
$this->apiKey = $apiKey;
|
||||
}
|
||||
@@ -51,15 +64,15 @@ class _SendGrid extends \IPS\Email
|
||||
* Send the email
|
||||
*
|
||||
* @param mixed $to The member or email address, or array of members or email addresses, to send to
|
||||
* @param mixed $cc Addresses to CC (can also be email, member or array of either)
|
||||
* @param mixed $bcc Addresses to BCC (can also be email, member or array of either)
|
||||
* @param mixed $fromEmail The email address to send from. If NULL, default setting is used
|
||||
* @param mixed $fromName The name the email should appear from. If NULL, default setting is used
|
||||
* @param array $additionalHeaders The name the email should appear from. If NULL, default setting is used
|
||||
* @param mixed $cc Addresses to CC (can also be email, member or array of either)
|
||||
* @param mixed $bcc Addresses to BCC (can also be email, member or array of either)
|
||||
* @param mixed $fromEmail The email address to send from. If NULL, default setting is used
|
||||
* @param mixed $fromName The name the email should appear from. If NULL, default setting is used
|
||||
* @param array $additionalHeaders The name the email should appear from. If NULL, default setting is used
|
||||
* @return void
|
||||
* @throws \IPS\Email\Outgoing\Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function _send( $to, $cc=array(), $bcc=array(), $fromEmail = NULL, $fromName = NULL, $additionalHeaders = array() )
|
||||
public function _send( mixed $to, mixed $cc=array(), mixed $bcc=array(), mixed $fromEmail = NULL, mixed $fromName = NULL, array $additionalHeaders = array() ) : void
|
||||
{
|
||||
/* Initiate the request */
|
||||
$request = $this->_initRequest( $fromEmail, $fromName );
|
||||
@@ -80,11 +93,11 @@ class _SendGrid extends \IPS\Email
|
||||
/* Add the recipients */
|
||||
foreach ( array( 'to', 'cc', 'bcc' ) as $type )
|
||||
{
|
||||
if ( \is_array( $$type ) )
|
||||
if ( is_array( $$type ) )
|
||||
{
|
||||
foreach ( $$type as $recipient )
|
||||
{
|
||||
if ( $recipient instanceof \IPS\Member )
|
||||
if ( $recipient instanceof Member )
|
||||
{
|
||||
$request['personalizations'][0][ $type ][] = array( 'email' => $recipient->email, 'name' => $recipient->name );
|
||||
}
|
||||
@@ -97,7 +110,7 @@ class _SendGrid extends \IPS\Email
|
||||
elseif ( $$type )
|
||||
{
|
||||
$recipient = $$type;
|
||||
if ( $recipient instanceof \IPS\Member )
|
||||
if ( $recipient instanceof Member )
|
||||
{
|
||||
$request['personalizations'][0][ $type ][] = array( 'email' => $recipient->email, 'name' => $recipient->name );
|
||||
}
|
||||
@@ -115,50 +128,29 @@ class _SendGrid extends \IPS\Email
|
||||
$response = $this->_api( 'mail/send', $request );
|
||||
if ( isset( $response['errors'] ) )
|
||||
{
|
||||
throw new \IPS\Email\Outgoing\Exception( $response['errors'][0]['message'] );
|
||||
throw new Exception( $response['errors'][0]['message'] );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this email class usable?
|
||||
*
|
||||
* @param string $type Email Type
|
||||
* @return bool
|
||||
*/
|
||||
public static function isUsable( string $type ): bool
|
||||
{
|
||||
if( ( new \IPS\core\extensions\core\CommunityEnhancements\Sendgrid() )->enabled )
|
||||
{
|
||||
return match( true )
|
||||
{
|
||||
( $type == \IPS\Email::TYPE_TRANSACTIONAL OR $type == \IPS\Email::TYPE_LIST ) AND Settings::i()->sendgrid_use_for == 2 => true,
|
||||
$type == \IPS\Email::TYPE_BULK AND (int) Settings::i()->sendgrid_use_for > 0 => true,
|
||||
default => false
|
||||
};
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge and Send
|
||||
*
|
||||
* @param array $recipients Array where the keys are the email addresses to send to and the values are an array of variables to replace
|
||||
* @param mixed $fromEmail The email address to send from. If NULL, default setting is used. NOTE: This should always be a site-controlled domin. Some services like Sparkpost require the domain to be validated.
|
||||
* @param mixed $fromName The name the email should appear from. If NULL, default setting is used
|
||||
* @param array $additionalHeaders Additional headers to send. Merge tags can be used like in content.
|
||||
* @param \IPS\Lang|NULL $language The language the email content should be in
|
||||
* @param array $recipients Array where the keys are the email addresses to send to and the values are an array of variables to replace
|
||||
* @param mixed $fromEmail The email address to send from. If NULL, default setting is used. NOTE: This should always be a site-controlled domin. Some services like Sparkpost require the domain to be validated.
|
||||
* @param mixed $fromName The name the email should appear from. If NULL, default setting is used
|
||||
* @param array $additionalHeaders Additional headers to send. Merge tags can be used like in content.
|
||||
* @param Lang|NULL $language The language the email content should be in
|
||||
* @return int Number of successful sends
|
||||
*/
|
||||
public function mergeAndSend( $recipients, $fromEmail = NULL, $fromName = NULL, $additionalHeaders = array(), \IPS\Lang $language = NULL )
|
||||
public function mergeAndSend( array $recipients, mixed $fromEmail = NULL, mixed $fromName = NULL, array $additionalHeaders = array(), Lang $language = NULL ): int
|
||||
{
|
||||
/* Initiate the request */
|
||||
$request = $this->_initRequest( $fromEmail, $fromName );
|
||||
|
||||
/* Add the subject and content */
|
||||
$subject = $this->compileSubject( NULL, $language );
|
||||
$htmlContent = $this->compileContent( 'html', FALSE, $language );
|
||||
$plaintextContent = preg_replace( '/\*\|(.+?)\|\*/', '*|$1_plain|*', $this->compileContent( 'plaintext', FALSE, $language ) );
|
||||
$htmlContent = $this->compileContent( 'html', null, $language );
|
||||
$plaintextContent = preg_replace( '/\*\|(.+?)\|\*/', '*|$1_plain|*', $this->compileContent( 'plaintext', null, $language ) );
|
||||
$request['subject'] = preg_replace( '/\*\|(.+?)\|\*/', '*|$1_plain|*', $subject );
|
||||
$request['content'] = array(
|
||||
array(
|
||||
@@ -182,7 +174,7 @@ class _SendGrid extends \IPS\Email
|
||||
{
|
||||
$language->parseEmail( $v );
|
||||
$finalSubstitutions["*|{$k}_plain|*"] = $v;
|
||||
$finalSubstitutions["*|{$k}|*"] = htmlspecialchars( $v, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8', TRUE );
|
||||
$finalSubstitutions["*|{$k}|*"] = htmlspecialchars( $v, ENT_QUOTES | ENT_DISALLOWED, 'UTF-8' );
|
||||
}
|
||||
|
||||
$request['personalizations'][] = array(
|
||||
@@ -195,16 +187,16 @@ class _SendGrid extends \IPS\Email
|
||||
$request = $this->_modifyRequestDataWithHeaders( $request, $additionalHeaders );
|
||||
|
||||
/* Log emails sent */
|
||||
$this->_trackStatistics( \count( $addresses ) );
|
||||
$this->_trackStatistics( count( $addresses ) );
|
||||
|
||||
/* Send */
|
||||
try
|
||||
{
|
||||
$response = $this->_api( 'mail/send', $request );
|
||||
}
|
||||
catch( \IPS\Email\Outgoing\Exception $e )
|
||||
catch(Exception $e )
|
||||
{
|
||||
\IPS\Db::i()->insert( 'core_mail_error_logs', array(
|
||||
Db::i()->insert( 'core_mail_error_logs', array(
|
||||
'mlog_date' => time(),
|
||||
'mlog_to' => json_encode( $addresses ),
|
||||
'mlog_from' => $fromEmail ?: Settings::i()->email_out,
|
||||
@@ -221,8 +213,8 @@ class _SendGrid extends \IPS\Email
|
||||
$errorcount = 0;
|
||||
if ( isset( $response['errors'] ) )
|
||||
{
|
||||
$errorcount = \count( $response['errors'] );
|
||||
\IPS\Db::i()->insert( 'core_mail_error_logs', array(
|
||||
$errorcount = count( $response['errors'] );
|
||||
Db::i()->insert( 'core_mail_error_logs', array(
|
||||
'mlog_date' => time(),
|
||||
'mlog_to' => json_encode( $addresses ),
|
||||
'mlog_from' => $fromEmail ?: Settings::i()->email_out,
|
||||
@@ -234,10 +226,10 @@ class _SendGrid extends \IPS\Email
|
||||
) );
|
||||
}
|
||||
|
||||
$successCount = ( \count( $recipients ) - $errorcount );
|
||||
$successCount = ( count( $recipients ) - $errorcount );
|
||||
|
||||
/* Update ad impression count */
|
||||
\IPS\core\Advertisement::updateEmailImpressions( $successCount );
|
||||
Advertisement::updateEmailImpressions( $successCount );
|
||||
|
||||
return $successCount;
|
||||
}
|
||||
@@ -245,11 +237,11 @@ class _SendGrid extends \IPS\Email
|
||||
/**
|
||||
* Create a request
|
||||
*
|
||||
* @param mixed $fromEmail The email address to send from. If NULL, default setting is used. NOTE: This should always be a site-controlled domin. Some services like Sparkpost require the domain to be validated.
|
||||
* @param mixed $fromName The name the email should appear from. If NULL, default setting is used
|
||||
* @param mixed $fromEmail The email address to send from. If NULL, default setting is used. NOTE: This should always be a site-controlled domin. Some services like Sparkpost require the domain to be validated.
|
||||
* @param mixed $fromName The name the email should appear from. If NULL, default setting is used
|
||||
* @return array
|
||||
*/
|
||||
protected function _initRequest( $fromEmail = NULL, $fromName = NULL )
|
||||
protected function _initRequest( mixed $fromEmail = NULL, mixed $fromName = NULL ): array
|
||||
{
|
||||
$request = array(
|
||||
'personalizations' => array(),
|
||||
@@ -279,12 +271,12 @@ class _SendGrid extends \IPS\Email
|
||||
/**
|
||||
* Modify the request data that will be sent to the SparkPost API with header data
|
||||
*
|
||||
* @param array $request SparkPost API request data
|
||||
* @param array $additionalHeaders Additional headers to send
|
||||
* @param array $allowedTags The tags that we want to parse
|
||||
* @param array $request SparkPost API request data
|
||||
* @param array $additionalHeaders Additional headers to send
|
||||
* @param array $allowedTags The tags that we want to parse
|
||||
* @return array
|
||||
*/
|
||||
protected function _modifyRequestDataWithHeaders( $request, $additionalHeaders = array(), $allowedTags = array() )
|
||||
protected function _modifyRequestDataWithHeaders( array $request, array $additionalHeaders = array(), array $allowedTags = array() ): array
|
||||
{
|
||||
/* Do we have a Reply-To? */
|
||||
if ( isset( $additionalHeaders['Reply-To'] ) )
|
||||
@@ -297,7 +289,7 @@ class _SendGrid extends \IPS\Email
|
||||
|
||||
if ( $matches[1] )
|
||||
{
|
||||
if ( preg_match( '/^\=\?UTF\-8\?B\?(.+?)\?\=$/i', trim( $matches[1] ), $_matches ) )
|
||||
if ( preg_match( '/^=\?UTF-8\?B\?(.+?)\?=$/i', trim( $matches[1] ), $_matches ) )
|
||||
{
|
||||
$request['reply_to']['name'] = base64_decode( $_matches[1] );
|
||||
}
|
||||
@@ -319,7 +311,7 @@ class _SendGrid extends \IPS\Email
|
||||
unset( $additionalHeaders['To'] );
|
||||
unset( $additionalHeaders['CC'] );
|
||||
unset( $additionalHeaders['BCC'] );
|
||||
if ( \count( $additionalHeaders ) )
|
||||
if ( count( $additionalHeaders ) )
|
||||
{
|
||||
$request['headers'] = $additionalHeaders;
|
||||
}
|
||||
@@ -331,15 +323,15 @@ class _SendGrid extends \IPS\Email
|
||||
/**
|
||||
* Make API call
|
||||
*
|
||||
* @param string $method Method
|
||||
* @param array $args Arguments
|
||||
* @throws \IPS\Email\Outgoing\Exception Indicates an invalid JSON response or HTTP error
|
||||
* @return array|null
|
||||
* @param string $method Method
|
||||
* @param array|null $args Arguments
|
||||
* @return array|null
|
||||
*@throws Exception Indicates an invalid JSON response or HTTP error
|
||||
*/
|
||||
protected function _api( $method, $args=NULL )
|
||||
protected function _api(string $method, array $args=NULL ): ?array
|
||||
{
|
||||
$request = \IPS\Http\Url::external( 'https://api.sendgrid.com/v3/' . $method )
|
||||
->request( \IPS\LONG_REQUEST_TIMEOUT )
|
||||
$request = Url::external( 'https://api.sendgrid.com/v3/' . $method )
|
||||
->request( LONG_REQUEST_TIMEOUT )
|
||||
->setHeaders( array( 'Content-Type' => 'application/json', 'Authorization' => "Bearer {$this->apiKey}" ) );
|
||||
|
||||
try
|
||||
@@ -365,44 +357,20 @@ class _SendGrid extends \IPS\Email
|
||||
|
||||
return $response;
|
||||
}
|
||||
catch ( \IPS\Http\Request\Exception $e )
|
||||
catch ( Exception | RuntimeException $e )
|
||||
{
|
||||
throw new \IPS\Email\Outgoing\Exception( $e->getMessage(), $e->getCode() );
|
||||
}
|
||||
/* Capture json decoding errors */
|
||||
catch ( \RuntimeException $e )
|
||||
{
|
||||
throw new \IPS\Email\Outgoing\Exception( $e->getMessage(), $e->getCode() );
|
||||
throw new Exception($e->getMessage(), $e->getCode() );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API key scopes
|
||||
*
|
||||
* @return array
|
||||
* @return array|null
|
||||
*/
|
||||
public function scopes()
|
||||
public function scopes(): ?array
|
||||
{
|
||||
return $this->_api( 'scopes' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse/Save email form settings
|
||||
*
|
||||
* @param array $values settings array
|
||||
* @return array
|
||||
*/
|
||||
public static function processSettings( array $values ): array
|
||||
{
|
||||
if ( isset( $values['mail_method'] ) and $values['mail_method'] != 'sendgrid' and Settings::i()->sendgrid_use_for == 2 )
|
||||
{
|
||||
$values['sendgrid_use_for'] = 1;
|
||||
}
|
||||
elseif ( isset( $values['mail_method'] ) and $values['mail_method'] == 'sendgrid' )
|
||||
{
|
||||
$values['sendgrid_use_for'] = 2;
|
||||
}
|
||||
|
||||
return $values;
|
||||
}
|
||||
|
||||
}
|
||||
+73
-119
@@ -10,77 +10,79 @@
|
||||
|
||||
namespace IPS\Email\Outgoing;
|
||||
|
||||
use IPS\Helpers\Form\Number;
|
||||
use IPS\Helpers\Form\Password;
|
||||
use IPS\Helpers\Form\Select;
|
||||
use IPS\Helpers\Form\Text;
|
||||
use IPS\Login;
|
||||
use IPS\Http\Url;
|
||||
use IPS\Settings;
|
||||
|
||||
/* To prevent PHP errors (extending class does not exist) revealing path */
|
||||
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
|
||||
use IPS\Email;
|
||||
use IPS\Request;
|
||||
use IPS\Settings;
|
||||
use function defined;
|
||||
use function intval;
|
||||
use function stream_socket_enable_crypto;
|
||||
use const IPS\DEFAULT_REQUEST_TIMEOUT;
|
||||
use const IPS\VERY_LONG_REQUEST_TIMEOUT;
|
||||
|
||||
if ( !defined( '\IPS\SUITE_UNIQUE_KEY' ) )
|
||||
{
|
||||
header( ( isset( $_SERVER['SERVER_PROTOCOL'] ) ? $_SERVER['SERVER_PROTOCOL'] : 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
header( ( $_SERVER['SERVER_PROTOCOL'] ?? 'HTTP/1.0' ) . ' 403 Forbidden' );
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief SMTP Email Class
|
||||
*/
|
||||
class _SMTP extends \IPS\Email
|
||||
class Smtp extends Email
|
||||
{
|
||||
/**
|
||||
* @brief SMTP Protocol ("tls", "ssl" or "plain")
|
||||
*/
|
||||
protected $smtpProtocol;
|
||||
protected string $smtpProtocol;
|
||||
|
||||
/**
|
||||
* @brief SMTP Host
|
||||
*/
|
||||
protected $smtpHost;
|
||||
protected string $smtpHost;
|
||||
|
||||
/**
|
||||
* @brief SMTP Port
|
||||
*/
|
||||
protected $smtpPort;
|
||||
protected int $smtpPort;
|
||||
|
||||
/**
|
||||
* @brief SMTP Username
|
||||
*/
|
||||
protected $smtpUser;
|
||||
protected string $smtpUser;
|
||||
|
||||
/**
|
||||
* @brief SMTP Password
|
||||
*/
|
||||
protected $smtpPass;
|
||||
protected string $smtpPass;
|
||||
|
||||
/**
|
||||
* @brief SMTP Connections
|
||||
*/
|
||||
protected static $smtp = array();
|
||||
protected static array $smtp = array();
|
||||
|
||||
/**
|
||||
* @brief Connection Key
|
||||
*/
|
||||
protected $connectionKey;
|
||||
protected string $connectionKey;
|
||||
|
||||
/**
|
||||
* @brief Log
|
||||
*/
|
||||
protected $log = '';
|
||||
protected string $log = '';
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param string $smtpProtocol Protocol to use
|
||||
* @param string $smtpHost Hostname to connect to
|
||||
* @param int $smtpPort Port to connect to
|
||||
* @param string $smtpUser Username
|
||||
* @param string $smtpPass Password
|
||||
* @param string $smtpProtocol Protocol to use
|
||||
* @param string $smtpHost Hostname to connect to
|
||||
* @param int $smtpPort Port to connect to
|
||||
* @param string $smtpUser Username
|
||||
* @param string $smtpPass Password
|
||||
* @return void
|
||||
*/
|
||||
public function __construct( $smtpProtocol, $smtpHost, $smtpPort, $smtpUser, $smtpPass )
|
||||
public function __construct( string $smtpProtocol, string $smtpHost, int $smtpPort, string $smtpUser, string $smtpPass )
|
||||
{
|
||||
$this->smtpProtocol = $smtpProtocol;
|
||||
$this->smtpHost = $smtpHost;
|
||||
@@ -93,10 +95,10 @@ class _SMTP extends \IPS\Email
|
||||
/**
|
||||
* Connect to server
|
||||
*
|
||||
* @param bool $checkSsl If set to FALSE, will skip peer certificate verification for TLS connections
|
||||
* @param bool $checkSsl If set to FALSE, will skip peer certificate verification for TLS connections
|
||||
* @return void
|
||||
*/
|
||||
public function connect( $checkSsl=TRUE )
|
||||
public function connect( bool $checkSsl=TRUE ) : void
|
||||
{
|
||||
/* Do we already have a connection? */
|
||||
if( array_key_exists( $this->connectionKey, static::$smtp ) )
|
||||
@@ -105,10 +107,10 @@ class _SMTP extends \IPS\Email
|
||||
}
|
||||
|
||||
/* Connect */
|
||||
$connection = @fsockopen( ( ( $this->smtpProtocol == 'ssl' ) ? 'ssl://' : '' ) . $this->smtpHost, $this->smtpPort, $errno, $errstr, \IPS\Request::isCliEnvironment() ? \IPS\VERY_LONG_REQUEST_TIMEOUT : \IPS\DEFAULT_REQUEST_TIMEOUT );
|
||||
$connection = @fsockopen( ( ( $this->smtpProtocol == 'ssl' ) ? 'ssl://' : '' ) . $this->smtpHost, $this->smtpPort, $errno, $errstr, Request::isCliEnvironment() ? VERY_LONG_REQUEST_TIMEOUT : DEFAULT_REQUEST_TIMEOUT );
|
||||
if ( !$connection )
|
||||
{
|
||||
throw new \IPS\Email\Outgoing\Exception( $errstr, $errno );
|
||||
throw new Exception( $errstr, $errno );
|
||||
}
|
||||
static::$smtp[ $this->connectionKey ] = $connection;
|
||||
register_shutdown_function(function( $object ){
|
||||
@@ -122,20 +124,19 @@ class _SMTP extends \IPS\Email
|
||||
$responseCode = mb_substr( $announce, 0, 3 );
|
||||
if ( $responseCode != 220 )
|
||||
{
|
||||
throw new \IPS\Email\Outgoing\Exception( 'smtpmail_fsock_error_initial', 0, NULL, array( $responseCode ) );
|
||||
throw new Exception( 'smtpmail_fsock_error_initial', 0, NULL, array( $responseCode ) );
|
||||
}
|
||||
|
||||
/* HELO/EHLO */
|
||||
$ehloFqdn = \IPS\Http\Url::internal('')->data['host'];
|
||||
try
|
||||
{
|
||||
$helo = 'EHLO';
|
||||
$responseCode = $this->_sendCommand( 'EHLO ' . $ehloFqdn, 250 );
|
||||
$responseCode = $this->_sendCommand('EHLO ' . $this->smtpHost, 250);
|
||||
}
|
||||
catch ( \IPS\Email\Outgoing\Exception $e )
|
||||
catch (Exception $e )
|
||||
{
|
||||
$helo = 'HELO';
|
||||
$responseCode = $this->_sendCommand( 'HELO ' . $ehloFqdn, 250 );
|
||||
$responseCode = $this->_sendCommand('HELO ' . $this->smtpHost, 250);
|
||||
}
|
||||
|
||||
/* Is TLS being used? */
|
||||
@@ -146,8 +147,8 @@ class _SMTP extends \IPS\Email
|
||||
@stream_context_set_option( static::$smtp[ $this->connectionKey ], 'ssl', 'verify_peer', false );
|
||||
}
|
||||
|
||||
$this->_sendCommand( 'STARTTLS', 220 );
|
||||
if ( !@\stream_socket_enable_crypto( static::$smtp[ $this->connectionKey ], TRUE, STREAM_CRYPTO_METHOD_SSLv23_CLIENT ) )
|
||||
$this->_sendCommand('STARTTLS', 220);
|
||||
if ( !@stream_socket_enable_crypto( static::$smtp[ $this->connectionKey ], TRUE, STREAM_CRYPTO_METHOD_SSLv23_CLIENT ) )
|
||||
{
|
||||
if ( $checkSsl )
|
||||
{
|
||||
@@ -157,20 +158,20 @@ class _SMTP extends \IPS\Email
|
||||
else
|
||||
{
|
||||
/* If it still failed on the second connection attempt, throw the exception */
|
||||
throw new \IPS\Email\Outgoing\Exception( 'smtpmail_tls_failed' );
|
||||
throw new Exception( 'smtpmail_tls_failed' );
|
||||
}
|
||||
}
|
||||
|
||||
/* Exchange server (at least) wants EHLO resending for STARTTLS */
|
||||
$this->_sendCommand( $helo . ' ' . $ehloFqdn, 250 );
|
||||
$this->_sendCommand($helo . ' ' . $this->smtpHost, 250);
|
||||
}
|
||||
|
||||
/* Authenticate */
|
||||
if ( $this->smtpUser )
|
||||
{
|
||||
$responseCode = $this->_sendCommand( 'AUTH LOGIN', 334 );
|
||||
$responseCode = $this->_sendCommand( base64_encode( $this->smtpUser ), 334 );
|
||||
$responseCode = $this->_sendCommand( base64_encode( $this->smtpPass ), 235 );
|
||||
$responseCode = $this->_sendCommand('AUTH LOGIN', 334);
|
||||
$responseCode = $this->_sendCommand(base64_encode($this->smtpUser), 334);
|
||||
$responseCode = $this->_sendCommand(base64_encode($this->smtpPass), 235);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,74 +179,69 @@ class _SMTP extends \IPS\Email
|
||||
* Send the email
|
||||
*
|
||||
* @param mixed $to The member or email address, or array of members or email addresses, to send to
|
||||
* @param mixed $cc Addresses to CC (can also be email, member or array of either)
|
||||
* @param mixed $bcc Addresses to BCC (can also be email, member or array of either)
|
||||
* @param mixed $fromEmail The email address to send from. If NULL, default setting is used
|
||||
* @param mixed $fromName The name the email should appear from. If NULL, default setting is used
|
||||
* @param array $additionalHeaders Additional headers to send
|
||||
* @param mixed $cc Addresses to CC (can also be email, member or array of either)
|
||||
* @param mixed $bcc Addresses to BCC (can also be email, member or array of either)
|
||||
* @param mixed $fromEmail The email address to send from. If NULL, default setting is used
|
||||
* @param mixed $fromName The name the email should appear from. If NULL, default setting is used
|
||||
* @param array $additionalHeaders Additional headers to send
|
||||
* @return void
|
||||
* @throws \IPS\Email\Outgoing\Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function _send( $to, $cc=array(), $bcc=array(), $fromEmail = NULL, $fromName = NULL, $additionalHeaders = array() )
|
||||
public function _send( mixed $to, mixed $cc=array(), mixed $bcc=array(), mixed $fromEmail = NULL, mixed $fromName = NULL, array $additionalHeaders = array() ) : void
|
||||
{
|
||||
/* Get the from email */
|
||||
$fromEmail = $fromEmail ?: Settings::i()->email_out;
|
||||
|
||||
/* SMTP requires you to do CC/BCC by sending a RCPT TO command for each recipient. We'll hide BCC by not actually setting that header */
|
||||
$recipientsForSmtp = explode( ',', static::_parseRecipients( $to, TRUE ) );
|
||||
$recipieintsForSmtp = explode( ',', static::_parseRecipients( $to, TRUE ) );
|
||||
if ( $cc )
|
||||
{
|
||||
$recipientsForSmtp = array_merge( $recipientsForSmtp, explode( ',', static::_parseRecipients( $cc, TRUE ) ) );
|
||||
$recipieintsForSmtp = array_merge( $recipieintsForSmtp, explode( ',', static::_parseRecipients( $cc, TRUE ) ) );
|
||||
}
|
||||
if ( $bcc )
|
||||
{
|
||||
$recipientsForSmtp = array_merge( $recipientsForSmtp, explode( ',', static::_parseRecipients( $bcc, TRUE ) ) );
|
||||
}
|
||||
$recipientsForSmtp = array_unique( array_map( 'trim', $recipientsForSmtp ) );
|
||||
|
||||
if( empty( $additonalHeaders['Message-ID'] ) )
|
||||
{
|
||||
$additionalHeaders['Message-ID'] = $this->generateMessageId();
|
||||
$recipieintsForSmtp = array_merge( $recipieintsForSmtp, explode( ',', static::_parseRecipients( $bcc, TRUE ) ) );
|
||||
}
|
||||
$recipieintsForSmtp = array_unique( array_map( 'trim', $recipieintsForSmtp ) );
|
||||
|
||||
/* Send */
|
||||
$this->_sendCompiled( $fromEmail, $recipientsForSmtp, $this->compileFullEmail( $to, $cc, array(), $fromEmail, $fromName, $additionalHeaders ) );
|
||||
$this->_sendCompiled( $fromEmail, $recipieintsForSmtp, $this->compileFullEmail( $to, $cc, array(), $fromEmail, $fromName, $additionalHeaders ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an email
|
||||
*
|
||||
* @param string $fromEmail The email address to send from
|
||||
* @param array $toEmails Array of email addresses to send to
|
||||
* @param string $email The full email (with headers, etc.) except the Bcc header
|
||||
* @param string $fromEmail The email address to send from
|
||||
* @param array $toEmails Array of email addresses to send to
|
||||
* @param string $email The full email (with headers, etc.) except the Bcc header
|
||||
* @return void
|
||||
* @throws \IPS\Email\Outgoing\Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
public function _sendCompiled( $fromEmail, $toEmails, $email )
|
||||
public function _sendCompiled( string $fromEmail, array $toEmails, string $email ) : void
|
||||
{
|
||||
$this->connect();
|
||||
|
||||
$this->_sendCommand( "MAIL FROM:<{$fromEmail}>", 250 );
|
||||
$this->_sendCommand("MAIL FROM:<{$fromEmail}>", 250);
|
||||
|
||||
foreach ( $toEmails as $toEmail )
|
||||
{
|
||||
$this->_sendCommand( "RCPT TO:<{$toEmail}>", 250, TRUE );
|
||||
$this->_sendCommand("RCPT TO:<{$toEmail}>", 250 );
|
||||
}
|
||||
|
||||
$this->_sendCommand( 'DATA', 354 );
|
||||
$this->_sendCommand( $email . "\r\n.", 250 );
|
||||
$this->_sendCommand('DATA', 354);
|
||||
$this->_sendCommand($email . "\r\n.", 250);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send SMTP Command
|
||||
*
|
||||
* @param string $command The command
|
||||
* @param int|NULL $expectedResponse The expected response code. Will throw an exception if different.
|
||||
* @param bool $resetOnFailure If the command fails, issue a RSET (reset) command
|
||||
* @param string $command The command
|
||||
* @param int|null $expectedResponse The expected response code. Will throw an exception if different.
|
||||
* @param bool $resetOnFailure If the command fails, issue a RSET (reset) command
|
||||
* @return string Response
|
||||
* @throws \IPS\Email\Outgoing\Exception
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function _sendCommand( $command, $expectedResponse=NULL, $resetOnFailure=FALSE )
|
||||
protected function _sendCommand( string $command, ?int $expectedResponse=NULL, bool $resetOnFailure = TRUE): string
|
||||
{
|
||||
/* Log */
|
||||
$this->log .= "> {$command}\r\n";
|
||||
@@ -257,15 +253,15 @@ class _SMTP extends \IPS\Email
|
||||
$response = $this->_getResponse();
|
||||
|
||||
/* Get response code */
|
||||
$code = \intval( mb_substr( $response, 0, 3 ) );
|
||||
$code = intval( mb_substr( $response, 0, 3 ) );
|
||||
if ( $expectedResponse !== NULL and $code !== $expectedResponse )
|
||||
{
|
||||
if( $resetOnFailure === TRUE )
|
||||
{
|
||||
$this->_sendCommand( 'RSET' );
|
||||
$this->_sendCommand('RSET');
|
||||
}
|
||||
|
||||
throw new \IPS\Email\Outgoing\Exception( $response, $code );
|
||||
throw new Exception( $response, $code );
|
||||
}
|
||||
|
||||
/* Return */
|
||||
@@ -277,7 +273,7 @@ class _SMTP extends \IPS\Email
|
||||
*
|
||||
* @return string Response
|
||||
*/
|
||||
protected function _getResponse()
|
||||
protected function _getResponse(): string
|
||||
{
|
||||
/* Read */
|
||||
$response = '';
|
||||
@@ -296,56 +292,14 @@ class _SMTP extends \IPS\Email
|
||||
/* Return */
|
||||
return $response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate random message-id
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function generateMessageId(): string
|
||||
{
|
||||
$randomString = base_convert( microtime(false ) . '.' . Login::generateRandomString(), 10, 36 );
|
||||
|
||||
return '<' . $randomString . '@' . Url::internal('')->data[ Url::COMPONENT_HOST ] . '>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SMTP log
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getLog()
|
||||
public function getLog(): string
|
||||
{
|
||||
return $this->log;
|
||||
}
|
||||
|
||||
/**
|
||||
* Form fields for email handler
|
||||
* These will automatically toggle on when the email handler is selected to be used
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function form(): array
|
||||
{
|
||||
$form = [];
|
||||
$options = array( 'plain' => 'smtp_plaintext', 'ssl' => 'smtp_ssl', 'tls' => 'smtp_tls' );
|
||||
$form['smtp_host'] = new Text( 'smtp_host', Settings::i()->smtp_host, FALSE, [], NULL, NULL, NULL, 'smtp_host' );
|
||||
$form['smtp_protocol'] = new Select( 'smtp_protocol', Settings::i()->smtp_protocol, FALSE, [ 'options' => $options ], NULL, NULL, NULL, 'smtp_protocol' );
|
||||
$form['smtp_port'] = new Number( 'smtp_port', Settings::i()->smtp_port, FALSE, [], NULL, NULL, NULL, 'smtp_port' );
|
||||
$form['smtp_user'] = new Text( 'smtp_user', Settings::i()->smtp_user, FALSE, [], NULL, NULL, NULL, 'smtp_user' );
|
||||
$form['smtp_pass'] = new Password( 'smtp_pass', Settings::i()->smtp_pass, FALSE, [ 'enforceMaxLimit' => FALSE ], NULL, NULL, NULL, 'smtp_pass' ) ;
|
||||
|
||||
return $form;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is this email class usable?
|
||||
*
|
||||
* @param string $type Email Type
|
||||
* @return bool
|
||||
*/
|
||||
public static function isUsable( string $type ): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user