Version 4.7.23

This commit is contained in:
Neo committed 2025-12-19 16:21:27 -08:00
1 parent 7124a02564
commit 25ddeb65d6
1791 files changed
+76990 -44452

No files matched your search

+60 -18
View File
@@ -10,6 +10,8 @@
namespace IPS;
use IPS\Settings;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
@@ -92,26 +94,34 @@ abstract class _Email
*/
public static function classToUse( $type )
{
if( \defined( '\IPS\EMAIL_DEBUG_PATH' ) AND \IPS\EMAIL_DEBUG_PATH )
foreach( static::outgoingHandlers() as $key => $class )
{
return 'IPS\Email\Outgoing\Debug';
}
elseif ( \IPS\Settings::i()->sendgrid_api_key and ( \IPS\Settings::i()->mail_method === 'sendgrid' or ( \IPS\Settings::i()->sendgrid_use_for == 2 ) or ( \IPS\Settings::i()->sendgrid_use_for and $type === static::TYPE_BULK ) ) )
{
return 'IPS\Email\Outgoing\SendGrid';
}
elseif ( \IPS\Settings::i()->mail_method === 'smtp' )
{
return 'IPS\Email\Outgoing\Smtp';
}
elseif ( \IPS\CIC )
{
return 'IPS\Email\Outgoing\IpsCic';
}
else
{
return 'IPS\Email\Outgoing\Php';
if( ( Settings::i()->mail_method == $key AND $class::isUsable( $type ) )
OR ( $key == 'sendgrid' and $class::isUsable( $type ) ) // SendGrid is a special use case since it can send bulk mail only.
OR ( $key == 'debug' and $class::isUsable( \IPS\Email::TYPE_TRANSACTIONAL ) ) )
{
return $class;
}
}
/* Still here?, fallback to default method */
return !\IPS\CIC ? 'IPS\Email\Outgoing\Php' : 'IPS\cloud\Email';
}
/**
* Available Outgoing Email Handlers, in order of preference
*
* @return string[]
*/
public static function outgoingHandlers(): array
{
return [
'debug' => 'IPS\Email\Outgoing\Debug',
'postmark' => 'IPS\Email\Outgoing\Postmark',
'sendgrid' => 'IPS\Email\Outgoing\SendGrid',
'smtp' => 'IPS\Email\Outgoing\Smtp',
'php' => 'IPS\Email\Outgoing\Php'
];
}
/**
@@ -159,6 +169,8 @@ abstract class _Email
return new \IPS\Email\Outgoing\Debug( \IPS\EMAIL_DEBUG_PATH );
case 'IPS\Email\Outgoing\SendGrid':
return new \IPS\Email\Outgoing\SendGrid( \IPS\Settings::i()->sendgrid_api_key );
case 'IPS\Email\Outgoing\Postmark':
return new \IPS\Email\Outgoing\Postmark( \IPS\Settings::i()->postmark_server_api_key );
case 'IPS\Email\Outgoing\Smtp':
return new \IPS\Email\Outgoing\Smtp( \IPS\Settings::i()->smtp_protocol, \IPS\Settings::i()->smtp_host, \IPS\Settings::i()->smtp_port, \IPS\Settings::i()->smtp_user, \IPS\Settings::i()->smtp_pass );
default:
@@ -1126,6 +1138,14 @@ abstract class _Email
return isset( \IPS\Data\Store::i()->$storeKey );
}
}
/**
* Is this email class usable?
*
* @param string $type Email Type
* @return bool
*/
abstract public static function isUsable( string $type ): bool;
/* !Utilities */
@@ -1662,4 +1682,26 @@ abstract class _Email
static::$_failedMail[ $key ] = \IPS\Db::i()->select( 'count(mlog_id)', 'core_mail_error_logs', $where )->first();
return static::$_failedMail[ $key ];
}
/**
* 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
{
return [];
}
/**
* Parse/Save email form settings
*
* @param array $values settings array
* @return array
*/
public static function processSettings( array $values ): array
{
return $values;
}
}
+16
View File
@@ -65,4 +65,20 @@ class _Debug extends \IPS\Email
throw new \IPS\Email\Outgoing\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;
}
}
-95
View File
@@ -1,95 +0,0 @@
<?php
/**
* @brief IPS CiC 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 13 Dec 2016
*/
namespace IPS\Email\Outgoing;
/* 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;
}
/**
* IPS CiC Email Class
*/
class _IpsCic extends \IPS\Email
{
/* !Configuration */
/**
* @brief The number of emails that can be sent in one "go"
*/
const MAX_EMAILS_PER_GO = 200;
/**
* @brief IPS Cloud Email Endpoint
*/
const ENDPOINT = 'http://ips-cic-email.invisioncic.com/sendEmail.php';
/**
* @brief Community
*/
protected $community;
/**
* Constructor
*
* @return void
*/
public function __construct()
{
if ( \IPS\CIC and preg_match( '/^\/var\/www\/html\/(.+?)(?:\/|$)/i', \IPS\ROOT_PATH, $matches ) )
{
$this->community = $matches[1];
}
else
{
throw new \RuntimeException('Site not compatible with IPS Cloud 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
* @return void
* @throws \IPS\Email\Outgoing\Exception
*/
public function _send( $to, $cc=array(), $bcc=array(), $fromEmail = NULL, $fromName = NULL, $additionalHeaders = array() )
{
try
{
\IPS\Http\Url::external( static::ENDPOINT )->request()->post( array(
'siteId' => $this->community,
'to' => array_map( 'trim', explode( ',', static::_parseRecipients( $to, TRUE ) ) ),
'cc' => array_map( 'trim', explode( ',', static::_parseRecipients( $cc, TRUE ) ) ),
'bcc' => array_map( 'trim', explode( ',', static::_parseRecipients( $bcc, TRUE ) ) ),
'fromEmail' => $fromEmail ?: \IPS\Settings::i()->email_out,
'fromName' => $fromName ?: \IPS\Settings::i()->board_name,
'additionalHeaders' => $additionalHeaders,
'subject' => $this->compileSubject( static::_getMemberFromRecipients( $to ) ),
'html' => $this->compileContent( 'html', static::_getMemberFromRecipients( $to ) ),
'plaintext' => $this->compileContent( 'plaintext', static::_getMemberFromRecipients( $to ) ),
'precedence' => ( $this->type === static::TYPE_LIST ) ? 'list' : ( $this->type === static::TYPE_BULK ? 'bulk' : '' )
) );
}
catch( \IPS\Http\Request\Exception $e )
{
throw new \IPS\Email\Outgoing\Exception( $e->getMessage() );
}
}
}
+34 -1
View File
@@ -10,6 +10,9 @@
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' ) )
{
@@ -55,7 +58,7 @@ class _PHP extends \IPS\Email
try
{
if ( !mail( $this->_parseRecipients( $to, TRUE ), static::encodeHeader( $subject ), $this->_compileMessage( static::_getMemberFromRecipients( $to ), $boundary, "\r\n", 68 ), implode( "\r\n", $headers ), \IPS\Settings::i()->php_mail_extra ) )
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 )
{
@@ -73,4 +76,34 @@ class _PHP extends \IPS\Email
throw new \IPS\Email\Outgoing\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;
}
}
+347
View File
@@ -0,0 +1,347 @@
<?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 ];
}
}
+52 -10
View File
@@ -10,6 +10,8 @@
namespace IPS\Email\Outgoing;
use IPS\Settings;
/* To prevent PHP errors (extending class does not exist) revealing path */
if ( !\defined( '\IPS\SUITE_UNIQUE_KEY' ) )
{
@@ -116,6 +118,27 @@ class _SendGrid extends \IPS\Email
throw new \IPS\Email\Outgoing\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
@@ -184,7 +207,7 @@ class _SendGrid extends \IPS\Email
\IPS\Db::i()->insert( 'core_mail_error_logs', array(
'mlog_date' => time(),
'mlog_to' => json_encode( $addresses ),
'mlog_from' => $fromEmail ?: \IPS\Settings::i()->email_out,
'mlog_from' => $fromEmail ?: Settings::i()->email_out,
'mlog_subject' => $subject,
'mlog_content' => $htmlContent,
'mlog_resend_data' => NULL,
@@ -202,7 +225,7 @@ class _SendGrid extends \IPS\Email
\IPS\Db::i()->insert( 'core_mail_error_logs', array(
'mlog_date' => time(),
'mlog_to' => json_encode( $addresses ),
'mlog_from' => $fromEmail ?: \IPS\Settings::i()->email_out,
'mlog_from' => $fromEmail ?: Settings::i()->email_out,
'mlog_subject' => $subject,
'mlog_content' => $htmlContent,
'mlog_resend_data' => NULL,
@@ -231,23 +254,23 @@ class _SendGrid extends \IPS\Email
$request = array(
'personalizations' => array(),
'from' => array(
'email' => $fromEmail ?: \IPS\Settings::i()->email_out,
'name' => $fromName ?: \IPS\Settings::i()->board_name
'email' => $fromEmail ?: Settings::i()->email_out,
'name' => $fromName ?: Settings::i()->board_name
),
'tracking_settings' => array(
'click_tracking' => array(
'enable' => (bool) \IPS\Settings::i()->sendgrid_click_tracking,
'enable_text' => (bool) \IPS\Settings::i()->sendgrid_click_tracking,
'enable' => (bool) Settings::i()->sendgrid_click_tracking,
'enable_text' => (bool) Settings::i()->sendgrid_click_tracking,
),
'open_tracking' => array(
'enable' => (bool) \IPS\Settings::i()->sendgrid_click_tracking,
'enable' => (bool) Settings::i()->sendgrid_click_tracking,
)
)
);
if ( \IPS\Settings::i()->sendgrid_ip_pool )
if ( Settings::i()->sendgrid_ip_pool )
{
$request['ip_pool_name'] = \IPS\Settings::i()->sendgrid_ip_pool;
$request['ip_pool_name'] = Settings::i()->sendgrid_ip_pool;
}
return $request;
@@ -362,5 +385,24 @@ class _SendGrid extends \IPS\Email
{
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;
}
}
+66 -10
View File
@@ -10,6 +10,14 @@
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' ) )
{
@@ -97,7 +105,7 @@ class _SMTP extends \IPS\Email
}
/* Connect */
$connection = @fsockopen( ( ( $this->smtpProtocol == 'ssl' ) ? 'ssl://' : '' ) . $this->smtpHost, $this->smtpPort, $errno, $errstr );
$connection = @fsockopen( ( ( $this->smtpProtocol == 'ssl' ) ? 'ssl://' : '' ) . $this->smtpHost, $this->smtpPort, $errno, $errstr, \IPS\Request::isCliEnvironment() ? \IPS\VERY_LONG_REQUEST_TIMEOUT : \IPS\DEFAULT_REQUEST_TIMEOUT );
if ( !$connection )
{
throw new \IPS\Email\Outgoing\Exception( $errstr, $errno );
@@ -118,15 +126,16 @@ class _SMTP extends \IPS\Email
}
/* HELO/EHLO */
$ehloFqdn = \IPS\Http\Url::internal('')->data['host'];
try
{
$helo = 'EHLO';
$responseCode = $this->_sendCommand( 'EHLO ' . $this->smtpHost, 250 );
$responseCode = $this->_sendCommand( 'EHLO ' . $ehloFqdn, 250 );
}
catch ( \IPS\Email\Outgoing\Exception $e )
{
$helo = 'HELO';
$responseCode = $this->_sendCommand( 'HELO ' . $this->smtpHost, 250 );
$responseCode = $this->_sendCommand( 'HELO ' . $ehloFqdn, 250 );
}
/* Is TLS being used? */
@@ -153,7 +162,7 @@ class _SMTP extends \IPS\Email
}
/* Exchange server (at least) wants EHLO resending for STARTTLS */
$this->_sendCommand( $helo . ' ' . $this->smtpHost, 250 );
$this->_sendCommand( $helo . ' ' . $ehloFqdn, 250 );
}
/* Authenticate */
@@ -180,22 +189,27 @@ class _SMTP extends \IPS\Email
public function _send( $to, $cc=array(), $bcc=array(), $fromEmail = NULL, $fromName = NULL, $additionalHeaders = array() )
{
/* Get the from email */
$fromEmail = $fromEmail ?: \IPS\Settings::i()->email_out;
$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 */
$recipieintsForSmtp = explode( ',', static::_parseRecipients( $to, TRUE ) );
$recipientsForSmtp = explode( ',', static::_parseRecipients( $to, TRUE ) );
if ( $cc )
{
$recipieintsForSmtp = array_merge( $recipieintsForSmtp, explode( ',', static::_parseRecipients( $cc, TRUE ) ) );
$recipientsForSmtp = array_merge( $recipientsForSmtp, explode( ',', static::_parseRecipients( $cc, TRUE ) ) );
}
if ( $bcc )
{
$recipieintsForSmtp = array_merge( $recipieintsForSmtp, explode( ',', static::_parseRecipients( $bcc, TRUE ) ) );
$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_unique( array_map( 'trim', $recipieintsForSmtp ) );
/* Send */
$this->_sendCompiled( $fromEmail, $recipieintsForSmtp, $this->compileFullEmail( $to, $cc, array(), $fromEmail, $fromName, $additionalHeaders ) );
$this->_sendCompiled( $fromEmail, $recipientsForSmtp, $this->compileFullEmail( $to, $cc, array(), $fromEmail, $fromName, $additionalHeaders ) );
}
/**
@@ -282,6 +296,18 @@ 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
@@ -292,4 +318,34 @@ class _SMTP extends \IPS\Email
{
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;
}
}