Version 4.3.0

This commit is contained in:
Neo committed 2025-12-19 05:47:12 -08:00
1 parent 96997ddd8e
commit fe1acf984a
1349 files changed
+116159 -67711

No files matched your search

+67 -24
View File
@@ -81,10 +81,6 @@ abstract class _Email
{
return 'IPS\Email\Outgoing\SendGrid';
}
elseif ( \IPS\Settings::i()->sparkpost_api_key and ( \IPS\Settings::i()->mail_method === 'sparkpost' or ( \IPS\Settings::i()->sparkpost_use_for == 2 ) or ( \IPS\Settings::i()->sparkpost_use_for and $type === static::TYPE_BULK ) ) )
{
return 'IPS\Email\Outgoing\SparkPost';
}
elseif ( \IPS\Settings::i()->mail_method === 'smtp' )
{
return 'IPS\Email\Outgoing\Smtp';
@@ -114,8 +110,6 @@ 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\SparkPost':
return new \IPS\Email\Outgoing\SparkPost( \IPS\Settings::i()->sparkpost_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:
@@ -281,9 +275,16 @@ abstract class _Email
{
$unsubscribe = static::template( $this->unsubscribeApp, $this->unsubscribeKey, $type, array_merge( $this->unsubscribeParams, array( $member, $this ) ) );
}
/* Get our picks */
$ourPicks = NULL;
if( \IPS\Settings::i()->our_picks_in_email and $this->type !== 'transactional' )
{
$ourPicks = \IPS\core\Promote::internalStream( 4 );
}
/* Wrap */
$return = static::template( 'core', 'emailWrapper', $type, array( $this->subject, $member ?: new \IPS\Member, $return, $unsubscribe, $member === FALSE, '', $this ) );
$return = static::template( 'core', 'emailWrapper', $type, array( $this->subject, $member ?: new \IPS\Member, $return, $unsubscribe, $member === FALSE, '', $this, $ourPicks ) );
}
/* Parse language */
@@ -351,7 +352,7 @@ abstract class _Email
*/
public function compileFullEmail( $to, $cc=array(), $bcc=array(), $fromEmail = NULL, $fromName = NULL, $additionalHeaders = array(), $eol = "\r\n", $lineLimit = 998 )
{
$boundary = "--==_mimepart_" . md5( uniqid() );
$boundary = "--==_mimepart_" . md5( mt_rand() );
$return = '';
@@ -396,13 +397,17 @@ abstract class _Email
/* Basic headers */
$headers = array(
'MIME-Version' => '1.0',
'Auto-Submitted' => 'auto-generated', // This is to try to prevent auto-responders and delivery failure notifications from responding
'To' => static::_parseRecipients( $to, TRUE ),
'From' => static::encodeHeader( $fromName, $fromEmail ),
'Subject' => static::encodeHeader( $subject ),
'Date' => date('r'),
);
if ( $this->autoSubmitted )
{
$headers['Auto-Submitted'] = 'auto-generated'; // This is to try to prevent auto-responders and delivery failure notifications from responding]
}
/* CC/BCC */
if ( $cc )
{
@@ -479,6 +484,11 @@ abstract class _Email
return $return;
}
/**
* @brief Auto generated flag
*/
protected $autoSubmitted = TRUE;
/**
* Send the email
*
@@ -488,9 +498,10 @@ abstract class _Email
* @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
* @param boolean $autoSubmitted The email was auto-generated (yes for notification, bulk mail, no for contact us form)
* @return bool
*/
public function send( $to, $cc=array(), $bcc=array(), $fromEmail = NULL, $fromName = NULL, $additionalHeaders = array() )
public function send( $to, $cc=array(), $bcc=array(), $fromEmail = NULL, $fromName = NULL, $additionalHeaders = array(), $autoSubmitted = TRUE )
{
/* Check we have recipients */
if ( !static::_parseRecipients( $to, TRUE ) )
@@ -501,8 +512,10 @@ abstract class _Email
/* Send the email */
try
{
$this->autoSubmitted = $autoSubmitted;
/* Send */
$this->_send( $to, $cc, $bcc, $fromEmail, $fromName, $additionalHeaders );
$this->_send( $to, $cc, $bcc, $fromEmail, $fromName, $additionalHeaders);
/* If it was successful, reset the failure count */
if ( !isset( \IPS\Data\Store::i()->failedMailCount ) OR \IPS\Data\Store::i()->failedMailCount >= 1 )
@@ -521,7 +534,7 @@ abstract class _Email
$plaintext = $this->compileContent( 'plaintext', static::_getMemberFromRecipients( $to ) );
$fromEmail = $fromEmail ?: \IPS\Settings::i()->email_out;
$fromName = $fromName ?: \IPS\Settings::i()->board_name;
$boundary = "--==_mimepart_" . md5( uniqid() );
$boundary = "--==_mimepart_" . md5( mt_rand() );
\IPS\Db::i()->insert( 'core_mail_error_logs', array(
'mlog_date' => time(),
@@ -787,23 +800,28 @@ abstract class _Email
else
{
/* See if we found anything from the store */
$key = md5( $app . ';' . $key ) . '_email_html';
$htmlTemplate = ( isset( \IPS\Data\Store::i()->$key ) ) ? \IPS\Data\Store::i()->$key : NULL;
if( $htmlTemplate )
$storeKey = md5( $app . ';' . $key ) . '_email_html';
if ( isset( \IPS\Data\Store::i()->$storeKey ) )
{
return TRUE;
}
$key = md5( $app . ';' . $key ) . '_email_plaintext';
$plaintextTemplate = ( isset( \IPS\Data\Store::i()->$key ) ) ? \IPS\Data\Store::i()->$key : NULL;
if( $plaintextTemplate )
else
{
return TRUE;
/* Check Database */
try
{
\IPS\Db::i()->select( 'template_id', 'core_email_templates', array( 'template_app=? and template_name=?', $app, $key ) )->first();
return TRUE;
}
catch( \Exception $e )
{
/* Nothing, it's OK to return false because there is not a separate row for plaintext */
return FALSE;
}
}
return FALSE;
$storeKey = md5( $app . ';' . $key ) . '_email_plaintext';
return isset( \IPS\Data\Store::i()->$storeKey );
}
}
@@ -1063,9 +1081,34 @@ abstract class _Email
'ipsSpoiler' => '_parseElementForEmailSpoiler',
'ipsEmbeddedVideo' => '_parseElementForEmailEmbed',
'ipsImage' => '_parseElementForEmailImage',
'ipsAttachLink' => '_parseElementForEmailAttachment',
);
}
/**
* Makes HTML acceptable for use in emails: Attachments
*
* @param \DOMElement $node The element
* @param \DOMElement $parent The element's parent node
* @param \IPS\Lang $language Language
* @return string
*/
protected static function _parseElementForEmailAttachment( \DOMElement &$node, \DOMNode $parent, \IPS\Lang $language )
{
if ( $node->getAttribute('href') )
{
$url = $node->getAttribute('href');
$parsed = parse_url( $node->getAttribute('href') );
if ( !isset( $parsed['scheme'] ) )
{
$baseUrl = parse_url( \IPS\Settings::i()->base_url );
$url = $baseUrl['scheme'] . '://' . str_replace( '//', '', $url );
$node->setAttribute( 'href', $url );
}
}
}
/**
* Makes HTML acceptable for use in emails: Quotes
*
+25 -16
View File
@@ -378,32 +378,41 @@ class _Email
}
else
{
$filename = md5( uniqid() );
$filename = md5( mt_rand() );
}
/* Save the file */
$file = \IPS\File::create( 'core_Attachment', $filename, $data['body'] );
/* Add to the message */
if ( $file->isImage() )
try
{
if ( isset( $data['headers']['content-id'] ) and $contentId = trim( $data['headers']['content-id'], '<>' ) and isset( $this->imagePlaceholders[ $contentId ] ) )
/* Save the file */
$file = \IPS\File::create( 'core_Attachment', $filename, $data['body'] );
/* Add to the message */
if ( $file->isImage() )
{
$imageTag = "<img src='<fileStore.core_Attachment>/{$file}' class='ipsImage ipsImage_thumbnailed'>";
$this->message = preg_replace( "/<a href=\"#image-placeholder={$this->imagePlaceholders[ $contentId ]}\" rel=\"[^\"]*\"><\/a>/", $imageTag, $this->message );
if ( isset( $data['headers']['content-id'] ) and $contentId = trim( $data['headers']['content-id'], '<>' ) and isset( $this->imagePlaceholders[ $contentId ] ) )
{
$imageTag = "<img src='<fileStore.core_Attachment>/{$file}' class='ipsImage ipsImage_thumbnailed'>";
$this->message = preg_replace( "/<a href=\"#image-placeholder={$this->imagePlaceholders[ $contentId ]}\" rel=\"[^\"]*\"><\/a>/", $imageTag, $this->message );
}
else
{
$this->message .= "<img src='<fileStore.core_Attachment>/{$file}' class='ipsImage ipsImage_thumbnailed'>";
}
}
else
{
$this->message .= "<img src='<fileStore.core_Attachment>/{$file}' class='ipsImage ipsImage_thumbnailed'>";
$this->message .= "<a class='ipsAttachLink' href='<fileStore.core_Attachment>/{$file}'>{$file->originalFilename}</a>";
}
/* Save to the $attachments array and exit */
$this->attachments[] = $file;
}
else
catch( \Exception $e )
{
$this->message .= "<a class='ipsAttachLink' href='<fileStore.core_Attachment>/{$file}'>{$file->originalFilename}</a>";
\IPS\Log::log( $e, 'incoming_email' );
/* This could be a corrupt file, which we can't do anything about */
}
/* Save to the $attachments array and exit */
$this->attachments[] = $file;
}
}
}
@@ -508,7 +517,7 @@ class _Email
*/
protected function _replaceImageWithPlaceholder( $matches )
{
$id = md5( uniqid() );
$id = mt_rand();
$this->imagePlaceholders[ $matches[1] ] = $id;
return '<a href="#image-placeholder=' . $id . '" rel=""></a>';
}
+1 -9
View File
@@ -28,15 +28,7 @@ class _IpsCic extends \IPS\Email
* @brief The number of emails that can be sent in one "go"
*/
const MAX_EMAILS_PER_GO = 200;
/**
* @brief If sending a bulk email to more than MAX_EMAILS_PER_GO - does this
* class require waiting between cycles? For "standard" classes like
* PHP and SMTP, this will be TRUE - and will cause bulk mails to go
* to a class. For APIs like SparkPost, this can be FALSE
*/
const REQUIRES_TIME_BREAK = FALSE;
/**
* @brief IPS Cloud Email Endpoint
*/
+11 -3
View File
@@ -36,7 +36,7 @@ class _PHP extends \IPS\Email
*/
public function _send( $to, $cc=array(), $bcc=array(), $fromEmail = NULL, $fromName = NULL, $additionalHeaders = array() )
{
$boundary = "--==_mimepart_" . md5( uniqid() );
$boundary = "--==_mimepart_" . md5( mt_rand() );
$subject = $this->compileSubject( static::_getMemberFromRecipients( $to ) );
$headers = array();
@@ -53,8 +53,16 @@ class _PHP extends \IPS\Email
if ( !mail( $this->_parseRecipients( $to, TRUE ), static::encodeHeader( $subject ), $this->_compileMessage( static::_getMemberFromRecipients( $to ), $boundary, PHP_EOL, 68 ), implode( PHP_EOL, $headers ), \IPS\Settings::i()->php_mail_extra ) )
{
$error = error_get_last();
preg_match( "/^(\d+)/", $error['message'], $matches );
throw new \IPS\Email\Outgoing\Exception( $error['message'], isset( $matches[1] ) ? $matches[1] : NULL );
if ( $error )
{
preg_match( "/^(\d+)/", $error['message'], $matches );
throw new \IPS\Email\Outgoing\Exception( $error['message'], isset( $matches[1] ) ? $matches[1] : NULL );
}
else
{
/* If $error is null, mail() is probably disabled */
throw new \IPS\Email\Outgoing\Exception( \IPS\Member::loggedIn()->language()->addToStack( 'email_test_mailfunction_disabled', FALSE ) );
}
}
}
catch ( \Exception $e )
-364
View File
@@ -1,364 +0,0 @@
<?php
/**
* @brief SparkPost 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 17 Apr 2013
*/
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;
}
/**
* SparkPost Email Class
*/
class _SparkPost extends \IPS\Email
{
/* !Configuration */
/**
* @brief The number of emails that can be sent in one "go"
*/
const MAX_EMAILS_PER_GO = 100; // SparkPost "recommends" 2000 per cycle
/**
* @brief If sending a bulk email to more than MAX_EMAILS_PER_GO - does this
* class require waiting between cycles? For "standard" classes like
* PHP and SMTP, this will be TRUE - and will cause bulk mails to go
* to a class. For APIs like SparkPost, this can be FALSE
*/
const REQUIRES_TIME_BREAK = FALSE;
/**
* @brief API Key
*/
protected $apiKey;
/**
* Constructor
*
* @param string $apiKey API Key
* @return void
*/
public function __construct( $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() )
{
/* Compile the recipients */
$recipients = array();
$mainToAddress = NULL;
foreach ( array_map( 'trim', explode( ',', static::_parseRecipients( $to, TRUE ) ) ) as $address )
{
if ( $mainToAddress === NULL )
{
$mainToAddress = $address;
}
$recipients[] = array( 'address' => array( 'email' => $address ) );
}
if ( $cc )
{
foreach ( array_map( 'trim', explode( ',', static::_parseRecipients( $cc, TRUE ) ) ) as $address )
{
$recipients[] = array( 'address' => array( 'email' => $address, 'header_to' => $mainToAddress ) );
}
$additionalHeaders['Cc'] = static::_parseRecipients( $cc );
}
if ( $bcc )
{
foreach ( array_map( 'trim', explode( ',', static::_parseRecipients( $bcc, TRUE ) ) ) as $address )
{
$recipients[] = array( 'address' => array( 'email' => $address, 'header_to' => $mainToAddress ) );
}
}
/* Compile the API call request data */
$request = array(
'recipients' => $recipients,
'content' => array(
'html' => static::_escapeTemplateTags( $this->compileContent( 'html', static::_getMemberFromRecipients( $to ) ) ),
'text' => static::_escapeTemplateTags( $this->compileContent( 'plaintext', static::_getMemberFromRecipients( $to ) ) ),
'subject' => static::_escapeTemplateTags( $this->compileSubject( static::_getMemberFromRecipients( $to ) ) ),
'from' => array(
'email' => $fromEmail ?: \IPS\Settings::i()->email_out,
'name' => $fromName ?: \IPS\Settings::i()->board_name
)
),
'options' => array(
'transactional' => $this->type === static::TYPE_TRANSACTIONAL,
'open_tracking' => (bool) \IPS\Settings::i()->sparkpost_click_tracking,
'click_tracking' => (bool) \IPS\Settings::i()->sparkpost_click_tracking
)
);
if ( \IPS\Settings::i()->sparkpost_ip_pool )
{
$request['options']['ip_pool'] = \IPS\Settings::i()->sparkpost_ip_pool;
}
$request = $this->_modifyRequestDataWithHeaders( $request, $additionalHeaders );
/* Make API call */
$response = $this->_api( 'transmissions', $request );
if ( isset( $response['errors'] ) )
{
$error = $response['errors'][0]['message'];
if( isset( $response['errors'][0]['description'] ) )
{
$error .= ': ' . $response['errors'][0]['description'];
}
throw new \IPS\Email\Outgoing\Exception( $error, ( isset( $response['errors'][0]['code'] ) ) ? $response['errors'][0]['code'] : NULL );
}
}
/**
* 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
* @return array
*/
protected function _modifyRequestDataWithHeaders( $request, $additionalHeaders = array(), $allowedTags = array() )
{
/* Do we have a Reply-To? */
if ( isset( $additionalHeaders['Reply-To'] ) )
{
$request['content']['reply_to'] = static::_escapeTemplateTags( $additionalHeaders['Reply-To'], $allowedTags );
unset( $additionalHeaders['Reply-To'] );
}
/* Any other headers? */
unset( $additionalHeaders['Subject'] );
unset( $additionalHeaders['From'] );
unset( $additionalHeaders['To'] );
if ( count( $additionalHeaders ) )
{
$request['content']['headers'] = array_map( function( $v ) use ( $allowedTags ) {
return static::_escapeTemplateTags( $v, $allowedTags );
}, $additionalHeaders );
}
/* Return */
return $request;
}
/**
* 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 $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 )
{
/* Work out recipients */
$varNames = array();
$recipientsForSparkpost = array();
$addresses = array();
foreach ( $recipients as $address => $_vars )
{
$addresses[] = $address;
$vars = array();
foreach ( $_vars as $k => $v )
{
$language->parseEmail( $v );
$vars[ $k ] = $v;
if ( !in_array( $k, $varNames ) )
{
$varNames[] = $k;
}
}
$recipientsForSparkpost[] = array( 'address' => array( 'email' => $address ), 'substitution_data' => $vars );
}
/* Put tags into SparkPost format */
$htmlContent = str_replace( array( '*|', '|*' ), array( '{{{', '}}}' ), $this->compileContent( 'html', FALSE, $language ) );
$plaintextContent = str_replace( array( '*|', '|*' ), array( '{{{', '}}}' ), $this->compileContent( 'plaintext', FALSE, $language ) );
$subject = str_replace( array( '*|', '|*' ), array( '{{{', '}}}' ), $this->compileSubject( NULL, $language ) );
$_additionalHeaders = array();
foreach ( $additionalHeaders as $k => $v )
{
$_additionalHeaders[ $k ] = str_replace( array( '*|', '|*' ), array( '{{{', '}}}' ), $v );
}
/* Compile the API call request data */
$request = array(
'recipients' => $recipientsForSparkpost,
'content' => array(
'html' => static::_escapeTemplateTags( $htmlContent, $varNames ),
'text' => static::_escapeTemplateTags( $plaintextContent, $varNames ),
'subject' => static::_escapeTemplateTags( $subject, $varNames ),
'from' => array(
'email' => $fromEmail ?: \IPS\Settings::i()->email_out,
'name' => $fromName ?: \IPS\Settings::i()->board_name
)
),
'options' => array(
'transactional' => $this->type === static::TYPE_TRANSACTIONAL,
'open_tracking' => (bool) \IPS\Settings::i()->sparkpost_click_tracking,
'click_tracking' => (bool) \IPS\Settings::i()->sparkpost_click_tracking
)
);
if ( \IPS\Settings::i()->sparkpost_ip_pool )
{
$request['options']['ip_pool'] = \IPS\Settings::i()->sparkpost_ip_pool;
}
$request = $this->_modifyRequestDataWithHeaders( $request, $_additionalHeaders, $varNames );
/* Make API call */
try
{
$response = $this->_api( 'transmissions', $request );
}
catch( \IPS\Email\Outgoing\Exception $e )
{
\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_subject' => $subject,
'mlog_content' => $htmlContent,
'mlog_resend_data' => NULL,
'mlog_msg' => json_encode( $e->getMessage() ),
'mlog_smtp_log' => NULL
) );
return 0;
}
if ( isset( $response['errors'] ) )
{
\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_subject' => $subject,
'mlog_content' => $htmlContent,
'mlog_resend_data' => NULL,
'mlog_msg' => json_encode( $response['errors'] ),
'mlog_smtp_log' => NULL
) );
}
return ( isset( $response['results'] ) and isset( $response['results']['total_accepted_recipients'] ) ) ? $response['results']['total_accepted_recipients'] : 0;
}
/**
* Make API call
*
* @param string $method Method
* @param string $apiKey API Key
* @param array $args Arguments
* @throws \IPS\Email\Outgoing\Exception Indicates an invalid JSON response or HTTP error
* @return array
*/
protected function _api( $method, $args=NULL )
{
$request = \IPS\Http\Url::external( 'https://api.sparkpost.com/api/v1/' . $method )
->request( \IPS\LONG_REQUEST_TIMEOUT )
->setHeaders( array( 'Content-Type' => 'application/json', 'Authorization' => $this->apiKey ) );
try
{
if ( $args )
{
$response = $request->post( json_encode( $args ) );
}
else
{
$response = $request->get();
}
return $response->decodeJson();
}
catch ( \IPS\Http\Request\Exception $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() );
}
}
/**
* Escape template tags
*
* @param string $content The content
* @param array $allowedTags The tags that we want to parse
* @return array
* @see <a href="https://developers.sparkpost.com/api/#/introduction/substitutions-reference/escaping-start-and-end-tags">Escaping Start and End Tags</a>
*/
protected static function _escapeTemplateTags( $content , $allowedTags = array() )
{
/* Store the tags we're allowed */
$replacedTagsI = 0;
$replacedTagsStore = array();
$content = preg_replace_callback( '/\{\{\{(' . implode( '|', array_map( function( $val ) { return preg_quote( $val, '/' ); }, $allowedTags ) ) . ')\}\}\}/', function( $matches ) use ( &$replacedTagsI, &$replacedTagsStore )
{
$replacedTagsStore[ ++$replacedTagsI ] = $matches[0];
return '--rt-' . $replacedTagsI . '--';
}, $content );
/* Escape */
$content = str_replace( '{{{', '--ot--x--', $content );
$content = str_replace( '}}}', '--ct--x--', $content );
$content = str_replace( '{{', '--od--x--', $content );
$content = str_replace( '}}', '--cd--x--', $content );
$content = str_replace( '--ot--x--', '{{opening_triple_curly()}}', $content );
$content = str_replace( '--ct--x--', '{{closing_triple_curly()}}', $content );
$content = str_replace( '--od--x--', '{{opening_double_curly()}}', $content );
$content = str_replace( '--cd--x--', '{{closing_double_curly()}}', $content );
/* Put our {{{tags}}} back */
$content = preg_replace_callback( '/--rt-(.+?)--/', function( $matches ) use ( $replacedTagsStore )
{
return isset( $replacedTagsStore[ $matches[1] ] ) ? $replacedTagsStore[ $matches[1] ] : '';
}, $content );
/* Return */
return $content;
}
/**
* Get sending domains
*
* @return array
*/
public function sendingDomains()
{
return $this->_api( 'sending-domains' );
}
}