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
+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