From eb1d56f43942b3f49275005073919a701f13d109 Mon Sep 17 00:00:00 2001 From: Eric Espie Date: Mon, 16 May 2022 14:51:12 +0200 Subject: [PATCH] =?UTF-8?q?N=C2=B03169=20-=20Add=20feature=20to=20connect?= =?UTF-8?q?=20Gsuite=20mail=20box=20with=20OAuth=20N=C2=B02504=20-=20Add?= =?UTF-8?q?=20feature=20to=20connect=20Office=20mail=20box=20with=20OAuth2?= =?UTF-8?q?=20for=20Microsoft=20Graph=20N=C2=B05102=20-=20Allow=20to=20sen?= =?UTF-8?q?d=20emails=20(eg.=20notifications)=20using=20GSuite=20SMTP=20an?= =?UTF-8?q?d=20OAuth=20=20*=202.7=20migration?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/config.class.inc.php | 40 ++ core/email.class.inc.php | 459 ++---------------- lib/composer/ClassLoader.php | 2 +- lib/composer/autoload_classmap.php | 7 +- lib/composer/autoload_real.php | 9 +- lib/composer/autoload_static.php | 7 +- setup/email.test.php | 25 +- .../Controller/OAuth/OAuthAjaxController.php | 13 +- .../OAuth/OAuthWizardController.php | 21 +- .../OAuth/IOAuthClientResultDisplay.php | 2 +- .../Client/Smtp/SmtpOAuthLogin.php | 4 +- sources/Core/Email/EmailFactory.php | 14 + sources/Core/Email/EmailLaminas.php | 344 ++++++------- sources/Core/Email/EmailSwiftMailer.php | 15 +- .../TwigBase/Controller/Controller.php | 8 +- .../application/TwigBase/Twig/TwigHelper.php | 6 +- .../backoffice/oauth/DisplayConfig.html.twig | 2 +- 17 files changed, 309 insertions(+), 669 deletions(-) diff --git a/core/config.class.inc.php b/core/config.class.inc.php index af1e05c36..7c3743388 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -555,6 +555,46 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ), + 'email_transport_smtp.oauth.provider' => [ + 'type' => 'string', + 'description' => 'TODO', + 'default' => '', + 'value' => '', + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ], + 'email_transport_smtp.oauth.client_id' => [ + 'type' => 'string', + 'description' => 'TODO', + 'default' => '', + 'value' => '', + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ], + 'email_transport_smtp.oauth.client_secret' => [ + 'type' => 'string', + 'description' => 'TODO', + 'default' => '', + 'value' => '', + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ], + 'email_transport_smtp.oauth.access_token' => [ + 'type' => 'string', + 'description' => 'TODO', + 'default' => '', + 'value' => '', + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ], + 'email_transport_smtp.oauth.refresh_token' => [ + 'type' => 'string', + 'description' => 'TODO', + 'default' => '', + 'value' => '', + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ], 'email_css' => array( 'type' => 'string', 'description' => 'CSS that will override the standard stylesheet used for the notifications', diff --git a/core/email.class.inc.php b/core/email.class.inc.php index c303bf029..ee8a8dd41 100644 --- a/core/email.class.inc.php +++ b/core/email.class.inc.php @@ -24,11 +24,7 @@ * @license http://opensource.org/licenses/AGPL-3.0 */ -use Pelago\Emogrifier\CssInliner; -use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter; -use Pelago\Emogrifier\HtmlProcessor\HtmlPruner; - -Swift_Preferences::getInstance()->setCharset('UTF-8'); +use Combodo\iTop\Core\Email\EmailFactory; define ('EMAIL_SEND_OK', 0); @@ -37,29 +33,16 @@ define ('EMAIL_SEND_ERROR', 2); class EMail { + protected $oMailer; + // Serialization formats const ORIGINAL_FORMAT = 1; // Original format, consisting in serializing the whole object, inculding the Swift Mailer's object. - // Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string + // Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string const FORMAT_V2 = 2; // New format, only the raw data are serialized (base64 encoded if needed) - - protected static $m_oConfig = null; - protected $m_aData; // For storing data to serialize - - public function LoadConfig($sConfigFile = ITOP_DEFAULT_CONFIG_FILE) - { - if (is_null(self::$m_oConfig)) - { - self::$m_oConfig = new Config($sConfigFile); - } - } - - protected $m_oMessage; public function __construct() { - $this->m_aData = array(); - $this->m_oMessage = new Swift_Message(); - $this->SetRecipientFrom(MetaModel::GetConfig()->Get('email_default_sender_address'), MetaModel::GetConfig()->Get('email_default_sender_label')); + $this->oMailer = EmailFactory::GetMailer(); } /** @@ -70,481 +53,97 @@ class EMail */ public function SerializeV2() { - return serialize($this->m_aData); + return $this->oMailer->SerializeV2(); } - + /** * Custom de-serialization method + * * @param string $sSerializedMessage The serialized representation of the message + * + * @return \Email + * @throws \ArchivedObjectException + * @throws \CoreException + * @throws \Symfony\Component\CssSelector\Exception\SyntaxErrorException */ static public function UnSerializeV2($sSerializedMessage) { - $aData = unserialize($sSerializedMessage); - $oMessage = new Email(); - - if (array_key_exists('body', $aData)) - { - $oMessage->SetBody($aData['body']['body'], $aData['body']['mimeType']); - } - if (array_key_exists('message_id', $aData)) - { - $oMessage->SetMessageId($aData['message_id']); - } - if (array_key_exists('bcc', $aData)) - { - $oMessage->SetRecipientBCC($aData['bcc']); - } - if (array_key_exists('cc', $aData)) - { - $oMessage->SetRecipientCC($aData['cc']); - } - if (array_key_exists('from', $aData)) - { - $oMessage->SetRecipientFrom($aData['from']['address'], $aData['from']['label']); - } - if (array_key_exists('reply_to', $aData)) - { - $oMessage->SetRecipientReplyTo($aData['reply_to']); - } - if (array_key_exists('to', $aData)) - { - $oMessage->SetRecipientTO($aData['to']); - } - if (array_key_exists('subject', $aData)) - { - $oMessage->SetSubject($aData['subject']); - } - - - if (array_key_exists('headers', $aData)) - { - foreach($aData['headers'] as $sKey => $sValue) - { - $oMessage->AddToHeader($sKey, $sValue); - } - } - if (array_key_exists('parts', $aData)) - { - foreach($aData['parts'] as $aPart) - { - $oMessage->AddPart($aPart['text'], $aPart['mimeType']); - } - } - if (array_key_exists('attachments', $aData)) - { - foreach($aData['attachments'] as $aAttachment) - { - $oMessage->AddAttachment(base64_decode($aAttachment['data']), $aAttachment['filename'], $aAttachment['mimeType']); - } - } - return $oMessage; - } - - protected function SendAsynchronous(&$aIssues, $oLog = null) - { - try - { - AsyncSendEmail::AddToQueue($this, $oLog); - } - catch(Exception $e) - { - $aIssues = array($e->GetMessage()); - return EMAIL_SEND_ERROR; - } - $aIssues = array(); - return EMAIL_SEND_PENDING; - } - - protected function SendSynchronous(&$aIssues, $oLog = null) - { - // If the body of the message is in HTML, embed all images based on attachments - $this->EmbedInlineImages(); - - $this->LoadConfig(); - - $sTransport = self::$m_oConfig->Get('email_transport'); - switch ($sTransport) - { - case 'SMTP': - $sHost = self::$m_oConfig->Get('email_transport_smtp.host'); - $sPort = self::$m_oConfig->Get('email_transport_smtp.port'); - $sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption'); - $sUserName = self::$m_oConfig->Get('email_transport_smtp.username'); - $sPassword = self::$m_oConfig->Get('email_transport_smtp.password'); - - $oTransport = new Swift_SmtpTransport($sHost, $sPort, $sEncryption); - if (strlen($sUserName) > 0) - { - $oTransport->setUsername($sUserName); - $oTransport->setPassword($sPassword); - } - break; - - case 'Null': - $oTransport = new Swift_NullTransport(); - break; - - case 'LogFile': - $oTransport = new Swift_LogFileTransport(); - $oTransport->setLogFile(APPROOT.'log/mail.log'); - break; - - case 'PHPMail': - default: - $oTransport = new Swift_SendmailTransport(); - } - - $oMailer = new Swift_Mailer($oTransport); - - $aFailedRecipients = array(); - $this->m_oMessage->setMaxLineLength(0); - $oKPI = new ExecutionKPI(); - try - { - $iSent = $oMailer->send($this->m_oMessage, $aFailedRecipients); - if ($iSent === 0) - { - // Beware: it seems that $aFailedRecipients sometimes contains the recipients that actually received the message !!! - IssueLog::Warning('Email sending failed: Some recipients were invalid, aFailedRecipients contains: '.implode(', ', $aFailedRecipients)); - $aIssues = array('Some recipients were invalid.'); - $oKPI->ComputeStats('Email Sent', 'Error received'); - return EMAIL_SEND_ERROR; - } - else - { - $aIssues = array(); - $oKPI->ComputeStats('Email Sent', 'Succeded'); - return EMAIL_SEND_OK; - } - } - catch (Exception $e) - { - $oKPI->ComputeStats('Email Sent', 'Error received'); - throw $e; - } - } - - /** - * Reprocess the body of the message (if it is an HTML message) - * to replace the URL of images based on attachments by a link - * to an embedded image (i.e. cid:....) - */ - protected function EmbedInlineImages() - { - if ($this->m_aData['body']['mimeType'] == 'text/html') - { - $oDOMDoc = new DOMDocument(); - $oDOMDoc->preserveWhitespace = true; - @$oDOMDoc->loadHTML(''.$this->m_aData['body']['body']); // For loading HTML chunks where the character set is not specified - - $oXPath = new DOMXPath($oDOMDoc); - $sXPath = '//img[@'.InlineImage::DOM_ATTR_ID.']'; - $oImagesList = $oXPath->query($sXPath); - - if ($oImagesList->length != 0) - { - foreach($oImagesList as $oImg) - { - $iAttId = $oImg->getAttribute(InlineImage::DOM_ATTR_ID); - $oAttachment = MetaModel::GetObject('InlineImage', $iAttId, false, true /* Allow All Data */); - if ($oAttachment) - { - $sImageSecret = $oImg->getAttribute('data-img-secret'); - $sAttachmentSecret = $oAttachment->Get('secret'); - if ($sImageSecret !== $sAttachmentSecret) - { - // @see N°1921 - // If copying from another iTop we could get an IMG pointing to an InlineImage with wrong secret - continue; - } - - $oDoc = $oAttachment->Get('contents'); - $oSwiftImage = new Swift_Image($oDoc->GetData(), $oDoc->GetFileName(), $oDoc->GetMimeType()); - $sCid = $this->m_oMessage->embed($oSwiftImage); - $oImg->setAttribute('src', $sCid); - } - } - } - $sHtmlBody = $oDOMDoc->saveHTML(); - $this->m_oMessage->setBody($sHtmlBody, 'text/html', 'UTF-8'); - } + return EmailFactory::GetMailer()::UnSerializeV2($sSerializedMessage); } public function Send(&$aIssues, $bForceSynchronous = false, $oLog = null) { - //select a default sender if none is provided. - if(empty($this->m_aData['from']['address']) && !empty($this->m_aData['to'])){ - $this->SetRecipientFrom($this->m_aData['to']); - } - - if ($bForceSynchronous) - { - return $this->SendSynchronous($aIssues, $oLog); - } - else - { - $bConfigASYNC = MetaModel::GetConfig()->Get('email_asynchronous'); - if ($bConfigASYNC) - { - return $this->SendAsynchronous($aIssues, $oLog); - } - else - { - return $this->SendSynchronous($aIssues, $oLog); - } - } + return $this->oMailer->Send($aIssues, $bForceSynchronous, $oLog); } public function AddToHeader($sKey, $sValue) { - if (!array_key_exists('headers', $this->m_aData)) - { - $this->m_aData['headers'] = array(); - } - $this->m_aData['headers'][$sKey] = $sValue; - - if (strlen($sValue) > 0) - { - $oHeaders = $this->m_oMessage->getHeaders(); - switch(strtolower($sKey)) - { - case 'return-path': - $this->m_oMessage->setReturnPath($sValue); - break; - - default: - $oHeaders->addTextHeader($sKey, $sValue); - } - } + $this->oMailer->AddToHeader($sKey, $sValue); } public function SetMessageId($sId) { - $this->m_aData['message_id'] = $sId; - - // Note: Swift will add the angle brackets for you - // so let's remove the angle brackets if present, for historical reasons - $sId = str_replace(array('<', '>'), '', $sId); - - $oMsgId = $this->m_oMessage->getHeaders()->get('Message-ID'); - $oMsgId->SetId($sId); + $this->oMailer->SetMessageId($sId); } public function SetReferences($sReferences) { - $this->AddToHeader('References', $sReferences); + $this->oMailer->SetReferences($sReferences); } public function SetBody($sBody, $sMimeType = 'text/html', $sCustomStyles = null) { - if (($sMimeType === 'text/html') && ($sCustomStyles !== null)) - { - $oDomDocument = CssInliner::fromHtml($sBody)->inlineCss($sCustomStyles)->getDomDocument(); - HtmlPruner::fromDomDocument($oDomDocument)->removeElementsWithDisplayNone(); - $sBody = CssToAttributeConverter::fromDomDocument($oDomDocument)->convertCssToVisualAttributes()->render(); // Adds html/body tags if not already present - } - $this->m_aData['body'] = array('body' => $sBody, 'mimeType' => $sMimeType); - $this->m_oMessage->setBody($sBody, $sMimeType); + $this->oMailer->SetBody($sBody, $sMimeType, $sCustomStyles); } public function AddPart($sText, $sMimeType = 'text/html') { - if (!array_key_exists('parts', $this->m_aData)) - { - $this->m_aData['parts'] = array(); - } - $this->m_aData['parts'][] = array('text' => $sText, 'mimeType' => $sMimeType); - $this->m_oMessage->addPart($sText, $sMimeType); + $this->oMailer->AddPart($sText, $sMimeType); } public function AddAttachment($data, $sFileName, $sMimeType) { - if (!array_key_exists('attachments', $this->m_aData)) - { - $this->m_aData['attachments'] = array(); - } - $this->m_aData['attachments'][] = array('data' => base64_encode($data), 'filename' => $sFileName, 'mimeType' => $sMimeType); - $this->m_oMessage->attach(new Swift_Attachment($data, $sFileName, $sMimeType)); + $this->oMailer->AddAttachment($data, $sFileName, $sMimeType); } public function SetSubject($sSubject) { - $this->m_aData['subject'] = $sSubject; - $this->m_oMessage->setSubject($sSubject); + $this->oMailer->SetSubject($sSubject); } public function GetSubject() { - return $this->m_oMessage->getSubject(); + return $this->oMailer->GetSubject(); } - /** - * Helper to transform and sanitize addresses - * - get rid of empty addresses - */ - protected function AddressStringToArray($sAddressCSVList) - { - $aAddresses = array(); - foreach(explode(',', $sAddressCSVList) as $sAddress) - { - $sAddress = trim($sAddress); - if (strlen($sAddress) > 0) - { - $aAddresses[] = $sAddress; - } - } - return $aAddresses; - } - public function SetRecipientTO($sAddress) { - $this->m_aData['to'] = $sAddress; - if (!empty($sAddress)) - { - $aAddresses = $this->AddressStringToArray($sAddress); - $this->m_oMessage->setTo($aAddresses); - } + $this->oMailer->SetRecipientTO($sAddress); } public function GetRecipientTO($bAsString = false) { - $aRes = $this->m_oMessage->getTo(); - if ($aRes === null) - { - // There is no "To" header field - $aRes = array(); - } - if ($bAsString) - { - $aStrings = array(); - foreach ($aRes as $sEmail => $sName) - { - if (is_null($sName)) - { - $aStrings[] = $sEmail; - } - else - { - $sName = str_replace(array('<', '>'), '', $sName); - $aStrings[] = "$sName <$sEmail>"; - } - } - return implode(', ', $aStrings); - } - else - { - return $aRes; - } + return $this->oMailer->GetRecipientTO($bAsString); } public function SetRecipientCC($sAddress) { - $this->m_aData['cc'] = $sAddress; - if (!empty($sAddress)) - { - $aAddresses = $this->AddressStringToArray($sAddress); - $this->m_oMessage->setCc($aAddresses); - } + $this->oMailer->SetRecipientCC($sAddress); } public function SetRecipientBCC($sAddress) { - $this->m_aData['bcc'] = $sAddress; - if (!empty($sAddress)) - { - $aAddresses = $this->AddressStringToArray($sAddress); - $this->m_oMessage->setBcc($aAddresses); - } + $this->oMailer->SetRecipientBCC($sAddress); } public function SetRecipientFrom($sAddress, $sLabel = '') { - $this->m_aData['from'] = array('address' => $sAddress, 'label' => $sLabel); - if ($sLabel != '') - { - $this->m_oMessage->setFrom(array($sAddress => $sLabel)); - } - else if (!empty($sAddress)) - { - $this->m_oMessage->setFrom($sAddress); - } + $this->oMailer->SetRecipientFrom($sAddress, $sLabel); } public function SetRecipientReplyTo($sAddress) { - $this->m_aData['reply_to'] = $sAddress; - if (!empty($sAddress)) - { - $this->m_oMessage->setReplyTo($sAddress); - } + $this->oMailer->SetRecipientReplyTo($sAddress); } -} - -///////////////////////////////////////////////////////////////////////////////////// - -/** - * Extension to SwiftMailer: "debug" transport that pretends messages have been sent, - * but just log them to a file. - * - * @package Swift - * @author Denis Flaven - */ -class Swift_Transport_LogFileTransport extends Swift_Transport_NullTransport -{ - protected $sLogFile; - - /** - * @inheritDoc - */ - public function send(Swift_Mime_SimpleMessage $message, &$failedRecipients = null) - { - $hFile = @fopen($this->sLogFile, 'a'); - if ($hFile) { - $sTxt = "================== ".date('Y-m-d H:i:s')." ==================\n"; - $sTxt .= $message->toString()."\n"; - - @fwrite($hFile, $sTxt); - @fclose($hFile); - } - - return parent::send($message, $failedRecipients); - } - - public function setLogFile($sFilename) - { - $this->sLogFile = $sFilename; - } -} - -/** - * Pretends messages have been sent, but just log them to a file. - * - * @package Swift - * @author Denis Flaven - */ -class Swift_LogFileTransport extends Swift_Transport_LogFileTransport -{ - /** - * Create a new LogFileTransport. - */ - public function __construct(Swift_Events_EventDispatcher $eventDispatcher) - { - parent::__construct($eventDispatcher); - call_user_func_array( - array($this, 'Swift_Transport_LogFileTransport::__construct'), - Swift_DependencyContainer::getInstance() - ->createDependenciesFor('transport.null') - ); - } - - /** - * Create a new LogFileTransport instance. - * - * @return Swift_LogFileTransport - */ - public static function newInstance() - { - return new self(); - } } \ No newline at end of file diff --git a/lib/composer/ClassLoader.php b/lib/composer/ClassLoader.php index afef3fa2a..0cd6055d1 100644 --- a/lib/composer/ClassLoader.php +++ b/lib/composer/ClassLoader.php @@ -149,7 +149,7 @@ class ClassLoader /** * @return string[] Array of classname => path - * @psalm-return array + * @psalm-var array */ public function getClassMap() { diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php index cbc92b05b..598a39495 100644 --- a/lib/composer/autoload_classmap.php +++ b/lib/composer/autoload_classmap.php @@ -160,6 +160,7 @@ return array( 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderFactory' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderGoogle' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientResultDisplayConf' => $baseDir . '/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php', + 'Combodo\\iTop\\Core\\Email\\EmailFactory' => $baseDir . '/sources/Core/Email/EmailFactory.php', 'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\DesignElement' => $baseDir . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\TwigExtension' => $baseDir . '/application/twigextension.class.inc.php', @@ -246,6 +247,7 @@ return array( 'DivisionByZeroError' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php', 'Doctrine\\Common\\Lexer\\AbstractLexer' => $vendorDir . '/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php', 'EMail' => $baseDir . '/core/email.class.inc.php', + 'EMailLaminas' => $baseDir . '/sources/Core/Email/EmailLaminas.php', 'Egulias\\EmailValidator\\EmailLexer' => $vendorDir . '/egulias/email-validator/src/EmailLexer.php', 'Egulias\\EmailValidator\\EmailParser' => $vendorDir . '/egulias/email-validator/src/EmailParser.php', 'Egulias\\EmailValidator\\EmailValidator' => $vendorDir . '/egulias/email-validator/src/EmailValidator.php', @@ -311,7 +313,6 @@ return array( 'Egulias\\EmailValidator\\Warning\\QuotedString' => $vendorDir . '/egulias/email-validator/src/Warning/QuotedString.php', 'Egulias\\EmailValidator\\Warning\\TLD' => $vendorDir . '/egulias/email-validator/src/Warning/TLD.php', 'Egulias\\EmailValidator\\Warning\\Warning' => $vendorDir . '/egulias/email-validator/src/Warning/Warning.php', - 'EmailFactory' => $baseDir . '/sources/Core/Email/EmailFactory.php', 'EmailSwiftMailer' => $baseDir . '/sources/Core/Email/EmailSwiftMailer.php', 'Error' => $vendorDir . '/symfony/polyfill-php70/Resources/stubs/Error.php', 'ErrorPage' => $baseDir . '/application/errorpage.class.inc.php', @@ -1265,8 +1266,8 @@ return array( 'StimulusInternal' => $baseDir . '/core/stimulus.class.inc.php', 'StimulusUserAction' => $baseDir . '/core/stimulus.class.inc.php', 'Str' => $baseDir . '/core/MyHelpers.class.inc.php', - 'Swift_LogFileTransport' => $baseDir . '/core/email.class.inc.php', - 'Swift_Transport_LogFileTransport' => $baseDir . '/core/email.class.inc.php', + 'Swift_LogFileTransport' => $baseDir . '/sources/Core/Email/EmailSwiftMailer.php', + 'Swift_Transport_LogFileTransport' => $baseDir . '/sources/Core/Email/EmailSwiftMailer.php', 'Symfony\\Bridge\\Twig\\AppVariable' => $vendorDir . '/symfony/twig-bridge/AppVariable.php', 'Symfony\\Bridge\\Twig\\Command\\DebugCommand' => $vendorDir . '/symfony/twig-bridge/Command/DebugCommand.php', 'Symfony\\Bridge\\Twig\\Command\\LintCommand' => $vendorDir . '/symfony/twig-bridge/Command/LintCommand.php', diff --git a/lib/composer/autoload_real.php b/lib/composer/autoload_real.php index 9a33b711c..661cd2543 100644 --- a/lib/composer/autoload_real.php +++ b/lib/composer/autoload_real.php @@ -60,16 +60,11 @@ class ComposerAutoloaderInit0018331147de7601e7552f7da8e3bb8b } } -/** - * @param string $fileIdentifier - * @param string $file - * @return void - */ function composerRequire0018331147de7601e7552f7da8e3bb8b($fileIdentifier, $file) { if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) { - $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; - require $file; + + $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true; } } diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php index 21bc64c73..94b13646f 100644 --- a/lib/composer/autoload_static.php +++ b/lib/composer/autoload_static.php @@ -528,6 +528,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderFactory' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderFactory.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientProviderGoogle' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientProviderGoogle.php', 'Combodo\\iTop\\Core\\Authentication\\Client\\OAuth\\OAuthClientResultDisplayConf' => __DIR__ . '/../..' . '/sources/Core/Authentication/Client/OAuth/OAuthClientResultDisplayConf.php', + 'Combodo\\iTop\\Core\\Email\\EmailFactory' => __DIR__ . '/../..' . '/sources/Core/Email/EmailFactory.php', 'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\DesignElement' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php', 'Combodo\\iTop\\TwigExtension' => __DIR__ . '/../..' . '/application/twigextension.class.inc.php', @@ -614,6 +615,7 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'DivisionByZeroError' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/DivisionByZeroError.php', 'Doctrine\\Common\\Lexer\\AbstractLexer' => __DIR__ . '/..' . '/doctrine/lexer/lib/Doctrine/Common/Lexer/AbstractLexer.php', 'EMail' => __DIR__ . '/../..' . '/core/email.class.inc.php', + 'EMailLaminas' => __DIR__ . '/../..' . '/sources/Core/Email/EmailLaminas.php', 'Egulias\\EmailValidator\\EmailLexer' => __DIR__ . '/..' . '/egulias/email-validator/src/EmailLexer.php', 'Egulias\\EmailValidator\\EmailParser' => __DIR__ . '/..' . '/egulias/email-validator/src/EmailParser.php', 'Egulias\\EmailValidator\\EmailValidator' => __DIR__ . '/..' . '/egulias/email-validator/src/EmailValidator.php', @@ -679,7 +681,6 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'Egulias\\EmailValidator\\Warning\\QuotedString' => __DIR__ . '/..' . '/egulias/email-validator/src/Warning/QuotedString.php', 'Egulias\\EmailValidator\\Warning\\TLD' => __DIR__ . '/..' . '/egulias/email-validator/src/Warning/TLD.php', 'Egulias\\EmailValidator\\Warning\\Warning' => __DIR__ . '/..' . '/egulias/email-validator/src/Warning/Warning.php', - 'EmailFactory' => __DIR__ . '/../..' . '/sources/Core/Email/EmailFactory.php', 'EmailSwiftMailer' => __DIR__ . '/../..' . '/sources/Core/Email/EmailSwiftMailer.php', 'Error' => __DIR__ . '/..' . '/symfony/polyfill-php70/Resources/stubs/Error.php', 'ErrorPage' => __DIR__ . '/../..' . '/application/errorpage.class.inc.php', @@ -1633,8 +1634,8 @@ class ComposerStaticInit0018331147de7601e7552f7da8e3bb8b 'StimulusInternal' => __DIR__ . '/../..' . '/core/stimulus.class.inc.php', 'StimulusUserAction' => __DIR__ . '/../..' . '/core/stimulus.class.inc.php', 'Str' => __DIR__ . '/../..' . '/core/MyHelpers.class.inc.php', - 'Swift_LogFileTransport' => __DIR__ . '/../..' . '/core/email.class.inc.php', - 'Swift_Transport_LogFileTransport' => __DIR__ . '/../..' . '/core/email.class.inc.php', + 'Swift_LogFileTransport' => __DIR__ . '/../..' . '/sources/Core/Email/EmailSwiftMailer.php', + 'Swift_Transport_LogFileTransport' => __DIR__ . '/../..' . '/sources/Core/Email/EmailSwiftMailer.php', 'Symfony\\Bridge\\Twig\\AppVariable' => __DIR__ . '/..' . '/symfony/twig-bridge/AppVariable.php', 'Symfony\\Bridge\\Twig\\Command\\DebugCommand' => __DIR__ . '/..' . '/symfony/twig-bridge/Command/DebugCommand.php', 'Symfony\\Bridge\\Twig\\Command\\LintCommand' => __DIR__ . '/..' . '/symfony/twig-bridge/Command/LintCommand.php', diff --git a/setup/email.test.php b/setup/email.test.php index 07bec1b2c..d435fccea 100644 --- a/setup/email.test.php +++ b/setup/email.test.php @@ -107,9 +107,9 @@ function CheckEmailSetting($oP) } } break; - + case 'SMTP': - $oP->info("iTop is configured to use the SMTP transport."); + $oP->info("iTop is configured to use the $sTransport transport."); $sHost = MetaModel::GetConfig()->Get('email_transport_smtp.host'); $sPort = MetaModel::GetConfig()->Get('email_transport_smtp.port'); $sEncryption = MetaModel::GetConfig()->Get('email_transport_smtp.encryption'); @@ -124,7 +124,26 @@ function CheckEmailSetting($oP) $oP->warning("The default settings may not be suitable for your environment. You may want to adjust these values by editing iTop's configuration file (".utils::GetConfigFilePathRelative().")."); } break; - + + case 'SMTP_OAuth': + $oP->info("iTop is configured to use the $sTransport transport."); + $sHost = MetaModel::GetConfig()->Get('email_transport_smtp.host'); + $sPort = MetaModel::GetConfig()->Get('email_transport_smtp.port'); + $sEncryption = MetaModel::GetConfig()->Get('email_transport_smtp.encryption'); + $sDisplayEncryption = empty($sEncryption) ? 'no encryption ' : $sEncryption; + $sUserName = MetaModel::GetConfig()->Get('email_transport_smtp.username'); + $sDisplayUserName = empty($sUserName) ? 'no user ' : $sUserName; + $sProvider = MetaModel::GetConfig()->Get('email_transport_smtp.oauth.provider'); + $sDisplayProvider = empty($sProvider) ? 'no Provider ' : $sProvider; + $sClientID = MetaModel::GetConfig()->Get('email_transport_smtp.oauth.client_id'); + $sDisplayClientID = empty($sClientID) ? 'no password ' : $sClientID; + $oP->info("SMTP configuration (from config-itop.php): host: $sHost, port: $sPort, provider: $sDisplayProvider, user: $sDisplayUserName, client id: $sDisplayClientID, encryption: $sDisplayEncryption."); + if (($sHost == 'localhost') && ($sPort == '25') && ($sUserName == '') && ($sClientID == '') && ($sProvider == '')) { + $oP->warning("The default settings may not be suitable for your environment. You may want to adjust these values by editing iTop's configuration file (".utils::GetConfigFilePathRelative().').'); + } + break; + + case 'Null': $oP->warning("iTop is configured to use the Null transport: emails sending will have no effect."); $bRet = false; diff --git a/sources/Controller/OAuth/OAuthAjaxController.php b/sources/Controller/OAuth/OAuthAjaxController.php index 8788c0d16..155cdd9bc 100644 --- a/sources/Controller/OAuth/OAuthAjaxController.php +++ b/sources/Controller/OAuth/OAuthAjaxController.php @@ -3,7 +3,6 @@ namespace Combodo\iTop\Controller\OAuth; use Combodo\iTop\Application\TwigBase\Controller\Controller; -use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderAbstract; use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderFactory; use utils; @@ -36,13 +35,17 @@ class OAuthAjaxController extends Controller $sAdditional = utils::ReadParam('additional', '', false, 'raw'); $sRedirectUrlQuery = parse_url($sRedirectUrl)['query']; - // TODO: Needs to handle mail to ticket part too - $aOAuthResultDisplayClasses = ['\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientResultDisplayConf']; + + $aOAuthResultDisplayClasses[] = '\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientResultDisplayConf'; + if (class_exists('Combodo\iTop\Extension\Service\OAuthClientResultDisplayMailbox')) { + $aOAuthResultDisplayClasses[] = 'Combodo\iTop\Extension\Service\OAuthClientResultDisplayMailbox'; + } + $aAdditional = []; parse_str($sAdditional, $aAdditional); - $sProviderClass = "\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProvider".$sProvider; - $sRedirectUrl = OAuthClientProviderAbstract::GetRedirectUri(); + // $sProviderClass = "\Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProvider".$sProvider; + // $sRedirectUrl = OAuthClientProviderAbstract::GetRedirectUri(); $aQuery = []; parse_str($sRedirectUrlQuery, $aQuery); diff --git a/sources/Controller/OAuth/OAuthWizardController.php b/sources/Controller/OAuth/OAuthWizardController.php index 13866ca0a..25265ea6b 100644 --- a/sources/Controller/OAuth/OAuthWizardController.php +++ b/sources/Controller/OAuth/OAuthWizardController.php @@ -9,11 +9,25 @@ namespace Combodo\iTop\Controller\OAuth; use Combodo\iTop\Application\TwigBase\Controller\Controller; use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientProviderAbstract; use Combodo\iTop\Core\Authentication\Client\OAuth\OAuthClientResultDisplayConf; +use Combodo\iTop\Extension\Service\OAuthClientResultDisplayMailbox; use Dict; use utils; class OAuthWizardController extends Controller { + public function __construct($sViewPath, $sModuleName = 'core') + { + $aAdditionalPaths = []; + + // Add extensions' template path + // TODO Rewrite in 3.1 with utils::GetClassesForInterface('Combodo\iTop\Core\Authentication\Client\OAuth\IOAuthClientResultDisplay', ...) + if (class_exists('Combodo\iTop\Extension\Service\OAuthClientResultDisplayMailbox')) { + $aAdditionalPaths[] = utils::GetAbsoluteModulePath('combodo-oauth-email-synchro').'templates'; + } + + parent::__construct($sViewPath, $sModuleName, $aAdditionalPaths); + } + public function OperationWizard() { $aParams = []; @@ -44,9 +58,10 @@ class OAuthWizardController extends Controller ]; // TODO: Needs to handle mail to ticket part too - $aParams['aAdditionalBlocks'] = [ - OAuthClientResultDisplayConf::GetResultDisplayTemplate(), - ]; + $aParams['aAdditionalBlocks'][] = OAuthClientResultDisplayConf::GetResultDisplayTemplate(); + if (class_exists('Combodo\iTop\Extension\Service\OAuthClientResultDisplayMailbox')) { + $aParams['aAdditionalBlocks'][] = OAuthClientResultDisplayMailbox::GetResultDisplayTemplate(); + } $this->DisplayPage($aParams); } diff --git a/sources/Core/Authentication/Client/OAuth/IOAuthClientResultDisplay.php b/sources/Core/Authentication/Client/OAuth/IOAuthClientResultDisplay.php index cf7701f39..e5111f38e 100644 --- a/sources/Core/Authentication/Client/OAuth/IOAuthClientResultDisplay.php +++ b/sources/Core/Authentication/Client/OAuth/IOAuthClientResultDisplay.php @@ -3,7 +3,7 @@ namespace Combodo\iTop\Core\Authentication\Client\OAuth; use League\OAuth2\Client\Token\AccessToken; interface IOAuthClientResultDisplay{ - public static function GetResultDisplayBlock(); + //public static function GetResultDisplayBlock(); public static function GetResultDisplayScript($sClientId, $sClientSecret, $sVendor, AccessToken $oAccessToken); public static function GetResultDisplayTemplate(); diff --git a/sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php b/sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php index e27ef7da8..1b0f7b721 100644 --- a/sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php +++ b/sources/Core/Authentication/Client/Smtp/SmtpOAuthLogin.php @@ -90,7 +90,7 @@ class Oauth extends Login try { while (true) { - $sResponse = $this->_receive(60); + $sResponse = $this->_receive(30); IssueLog::Debug("SMTP Oauth receiving $sResponse", static::LOG_CHANNEL); @@ -100,7 +100,7 @@ class Oauth extends Login } else { if (preg_match('/Unauthorized/i', $sResponse) || preg_match('/Rejected/i', $sResponse) || - preg_match('/^(535|432|454|534|500|530|538)/', $sResponse)) { + preg_match('/^(535|432|454|534|500|530|538|334)/', $sResponse)) { IssueLog::Error('Unable to authenticate for outgoing mails for provider '.self::$oProvider::GetVendorName()." Error: $sResponse", static::LOG_CHANNEL); return false; diff --git a/sources/Core/Email/EmailFactory.php b/sources/Core/Email/EmailFactory.php index 56d84f21b..6d1354d9b 100644 --- a/sources/Core/Email/EmailFactory.php +++ b/sources/Core/Email/EmailFactory.php @@ -1,6 +1,20 @@ Get('email_transport'); + if ($sTransport == 'SMTP_OAuth') { + return EMailLaminas::GetMailer(); + } + return EmailSwiftMailer::GetMailer(); + } } \ No newline at end of file diff --git a/sources/Core/Email/EmailLaminas.php b/sources/Core/Email/EmailLaminas.php index 9ed1a1a70..f828113a8 100644 --- a/sources/Core/Email/EmailLaminas.php +++ b/sources/Core/Email/EmailLaminas.php @@ -38,24 +38,19 @@ use Pelago\Emogrifier\CssInliner; use Pelago\Emogrifier\HtmlProcessor\CssToAttributeConverter; use Pelago\Emogrifier\HtmlProcessor\HtmlPruner; -define ('EMAIL_SEND_OK', 0); -define ('EMAIL_SEND_PENDING', 1); -define ('EMAIL_SEND_ERROR', 2); - -class EMail +class EMailLaminas { // Serialization formats const ORIGINAL_FORMAT = 1; // Original format, consisting in serializing the whole object, inculding the Swift Mailer's object. - // Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string + // Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string const FORMAT_V2 = 2; // New format, only the raw data are serialized (base64 encoded if needed) - + protected static $m_oConfig = null; protected $m_aData; // For storing data to serialize - public function LoadConfig($sConfigFile = ITOP_DEFAULT_CONFIG_FILE) + public static function LoadConfig($sConfigFile = ITOP_DEFAULT_CONFIG_FILE) { - if (is_null(self::$m_oConfig)) - { + if (is_null(self::$m_oConfig)) { self::$m_oConfig = new Config($sConfigFile); } } @@ -95,171 +90,160 @@ class EMail { $aData = unserialize($sSerializedMessage); $oMessage = new Email(); - - if (array_key_exists('body', $aData)) - { + + if (array_key_exists('body', $aData)) { $oMessage->SetBody($aData['body']['body'], $aData['body']['mimeType']); } - if (array_key_exists('message_id', $aData)) - { + if (array_key_exists('message_id', $aData)) { $oMessage->SetMessageId($aData['message_id']); } - if (array_key_exists('bcc', $aData)) - { + if (array_key_exists('bcc', $aData)) { $oMessage->SetRecipientBCC($aData['bcc']); } - if (array_key_exists('cc', $aData)) - { + if (array_key_exists('cc', $aData)) { $oMessage->SetRecipientCC($aData['cc']); } - if (array_key_exists('from', $aData)) - { + if (array_key_exists('from', $aData)) { $oMessage->SetRecipientFrom($aData['from']['address'], $aData['from']['label']); } - if (array_key_exists('reply_to', $aData)) - { + if (array_key_exists('reply_to', $aData)) { $oMessage->SetRecipientReplyTo($aData['reply_to']['address'], $aData['reply_to']['label']); } - if (array_key_exists('to', $aData)) - { + if (array_key_exists('to', $aData)) { $oMessage->SetRecipientTO($aData['to']); } - if (array_key_exists('subject', $aData)) - { + if (array_key_exists('subject', $aData)) { $oMessage->SetSubject($aData['subject']); } - - if (array_key_exists('headers', $aData)) - { - foreach($aData['headers'] as $sKey => $sValue) - { + + if (array_key_exists('headers', $aData)) { + foreach ($aData['headers'] as $sKey => $sValue) { $oMessage->AddToHeader($sKey, $sValue); } } - if (array_key_exists('parts', $aData)) - { - foreach($aData['parts'] as $aPart) - { + if (array_key_exists('parts', $aData)) { + foreach ($aData['parts'] as $aPart) { $oMessage->AddPart($aPart['text'], $aPart['mimeType']); } } - if (array_key_exists('attachments', $aData)) - { - foreach($aData['attachments'] as $aAttachment) - { + if (array_key_exists('attachments', $aData)) { + foreach ($aData['attachments'] as $aAttachment) { $oMessage->AddAttachment(base64_decode($aAttachment['data']), $aAttachment['filename'], $aAttachment['mimeType']); } } + return $oMessage; } - - protected function SendAsynchronous(&$aIssues, $oLog = null) + + protected function SendAsynchronous(&$aIssues, $oLog = null) { - try - { + try { AsyncSendEmail::AddToQueue($this, $oLog); } - catch(Exception $e) - { + catch (Exception $e) { $aIssues = array($e->GetMessage()); + return EMAIL_SEND_ERROR; } $aIssues = array(); + return EMAIL_SEND_PENDING; } + public static function GetMailer() + { + return new EMailLaminas(); + } + /** * @throws \Exception */ protected function SendSynchronous(&$aIssues, $oLog = null) { - + $this->LoadConfig(); $sTransport = self::$m_oConfig->Get('email_transport'); - switch ($sTransport) - { - case 'SMTP': - $sHost = self::$m_oConfig->Get('email_transport_smtp.host'); - $sPort = self::$m_oConfig->Get('email_transport_smtp.port'); - $sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption'); - $sUserName = self::$m_oConfig->Get('email_transport_smtp.username'); - $sPassword = self::$m_oConfig->Get('email_transport_smtp.password'); + switch ($sTransport) { + case 'SMTP': + $sHost = self::$m_oConfig->Get('email_transport_smtp.host'); + $sPort = self::$m_oConfig->Get('email_transport_smtp.port'); + $sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption'); + $sUserName = self::$m_oConfig->Get('email_transport_smtp.username'); + $sPassword = self::$m_oConfig->Get('email_transport_smtp.password'); - $oTransport = new Smtp(); - $aOptions= [ - 'host' => $sHost, - 'port' => $sPort, - 'connection_class' => 'login', - 'connection_config' => [ - 'ssl' => $sEncryption, - ], - ]; - if (strlen($sUserName) > 0) - { - $aOptions['connection_config']['username'] = $sUserName; - $aOptions['connection_config']['password'] = $sPassword; - } - $oOptions = new SmtpOptions($aOptions); - $oTransport->setOptions($oOptions); - break; - case 'SMTP_OAuth': - $sHost = self::$m_oConfig->Get('email_transport_smtp.host'); - $sPort = self::$m_oConfig->Get('email_transport_smtp.port'); - $sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption'); - $sUserName = self::$m_oConfig->Get('email_transport_smtp.username'); + $oTransport = new Smtp(); + $aOptions = [ + 'host' => $sHost, + 'port' => $sPort, + 'connection_class' => 'login', + 'connection_config' => [ + 'ssl' => $sEncryption, + ], + ]; + if (strlen($sUserName) > 0) { + $aOptions['connection_config']['username'] = $sUserName; + $aOptions['connection_config']['password'] = $sPassword; + } + $oOptions = new SmtpOptions($aOptions); + $oTransport->setOptions($oOptions); + break; + case 'SMTP_OAuth': + $sHost = self::$m_oConfig->Get('email_transport_smtp.host'); + $sPort = self::$m_oConfig->Get('email_transport_smtp.port'); + $sEncryption = self::$m_oConfig->Get('email_transport_smtp.encryption'); + $sUserName = self::$m_oConfig->Get('email_transport_smtp.username'); - $oTransport = new Smtp(); - $aOptions= [ - 'host' => $sHost, - 'port' => $sPort, - 'connection_class' => 'Laminas\Mail\Protocol\Smtp\Auth\Oauth', - 'connection_config' => [ - 'ssl' => $sEncryption, - ], - ]; - if (strlen($sUserName) > 0) - { - $aOptions['connection_config']['username'] = $sUserName; - } - $oOptions = new SmtpOptions($aOptions); - $oTransport->setOptions($oOptions); + $oTransport = new Smtp(); + $aOptions = [ + 'host' => $sHost, + 'port' => $sPort, + 'connection_class' => 'Laminas\Mail\Protocol\Smtp\Auth\Oauth', + 'connection_config' => [ + 'ssl' => $sEncryption, + ], + ]; + if (strlen($sUserName) > 0) { + $aOptions['connection_config']['username'] = $sUserName; + } + $oOptions = new SmtpOptions($aOptions); + $oTransport->setOptions($oOptions); - \Laminas\Mail\Protocol\Smtp\Auth\Oauth::setProvider(OAuthClientProviderFactory::getProviderForSMTP()); - break; - case 'Null': - $oTransport = new Smtp(); - break; - - case 'LogFile': - $oTransport = new File(); - $aOptions = new FileOptions([ - 'path' => APPROOT.'log/mail.log', - ]); - $oTransport->setOptions($aOptions); - break; - - case 'PHPMail': - default: - $oTransport = new Smtp(); + \Laminas\Mail\Protocol\Smtp\Auth\Oauth::setProvider(OAuthClientProviderFactory::getProviderForSMTP()); + break; + case 'Null': + $oTransport = new Smtp(); + break; + + case 'LogFile': + $oTransport = new File(); + $aOptions = new FileOptions([ + 'path' => APPROOT.'log/mail.log', + ]); + $oTransport->setOptions($aOptions); + break; + + case 'PHPMail': + default: + $oTransport = new Smtp(); } - + $oKPI = new ExecutionKPI(); - try - { + try { $oTransport->send($this->m_oMessage); $aIssues = array(); $oKPI->ComputeStats('Email Sent', 'Succeded'); + return EMAIL_SEND_OK; } - catch(Laminas\Mail\Transport\Exception\RuntimeException $e){ + catch (Laminas\Mail\Transport\Exception\RuntimeException $e) { IssueLog::Warning('Email sending failed: Some recipients were invalid'); $aIssues = array('Some recipients were invalid.'); $oKPI->ComputeStats('Email Sent', 'Error received'); + return EMAIL_SEND_ERROR; } - catch (Exception $e) - { + catch (Exception $e) { $oKPI->ComputeStats('Email Sent', 'Error received'); throw $e; } @@ -287,18 +271,14 @@ class EMail $oImagesList = $oXPath->query($sXPath); $oImagesContent = new \Laminas\Mime\Message(); $aImagesParts = []; - if ($oImagesList->length != 0) - { - foreach($oImagesList as $oImg) - { + if ($oImagesList->length != 0) { + foreach ($oImagesList as $oImg) { $iAttId = $oImg->getAttribute(InlineImage::DOM_ATTR_ID); $oAttachment = MetaModel::GetObject('InlineImage', $iAttId, false, true /* Allow All Data */); - if ($oAttachment) - { + if ($oAttachment) { $sImageSecret = $oImg->getAttribute('data-img-secret'); $sAttachmentSecret = $oAttachment->Get('secret'); - if ($sImageSecret !== $sAttachmentSecret) - { + if ($sImageSecret !== $sAttachmentSecret) { // @see N°1921 // If copying from another iTop we could get an IMG pointing to an InlineImage with wrong secret continue; @@ -314,7 +294,7 @@ class EMail $oNewAttachment->filename = $oDoc->GetFileName(); $oNewAttachment->disposition = Mime::DISPOSITION_INLINE; $oNewAttachment->encoding = Mime::ENCODING_BASE64; - + $oImagesContent->addPart($oNewAttachment); $oImg->setAttribute('src', 'cid:'.$sCid); $aImagesParts[] = $oNewAttachment; @@ -322,29 +302,24 @@ class EMail } } $sBody = $oDOMDoc->saveHTML(); + return $aImagesParts; } public function Send(&$aIssues, $bForceSynchronous = false, $oLog = null) { //select a default sender if none is provided. - if(empty($this->m_aData['from']['address']) && !empty($this->m_aData['to'])){ + if (empty($this->m_aData['from']['address']) && !empty($this->m_aData['to'])) { $this->SetRecipientFrom($this->m_aData['to']); } - if ($bForceSynchronous) - { + if ($bForceSynchronous) { return $this->SendSynchronous($aIssues, $oLog); - } - else - { + } else { $bConfigASYNC = MetaModel::GetConfig()->Get('email_asynchronous'); - if ($bConfigASYNC) - { + if ($bConfigASYNC) { return $this->SendAsynchronous($aIssues, $oLog); - } - else - { + } else { return $this->SendSynchronous($aIssues, $oLog); } } @@ -352,17 +327,14 @@ class EMail public function AddToHeader($sKey, $sValue) { - if (!array_key_exists('headers', $this->m_aData)) - { + if (!array_key_exists('headers', $this->m_aData)) { $this->m_aData['headers'] = array(); } $this->m_aData['headers'][$sKey] = $sValue; - - if (strlen($sValue) > 0) - { + + if (strlen($sValue) > 0) { $oHeaders = $this->m_oMessage->getHeaders(); - switch(strtolower($sKey)) - { + switch (strtolower($sKey)) { case 'return-path': $this->m_oMessage->setReturnPath($sValue); break; @@ -376,7 +348,7 @@ class EMail public function SetMessageId($sId) { $this->m_aData['message_id'] = $sId; - + // Note: Swift will add the angle brackets for you // so let's remove the angle brackets if present, for historical reasons $sId = str_replace(array('<', '>'), '', $sId); @@ -405,7 +377,7 @@ class EMail /** * Set current Email body and process inline images. - * + * * @param $sBody * @param string $sMimeType * @param $sCustomStyles @@ -419,7 +391,7 @@ class EMail { $oBody = new Laminas\Mime\Message(); $aAdditionalParts = []; - + if (($sMimeType === Mime::TYPE_HTML) && ($sCustomStyles !== null)) { $oDomDocument = CssInliner::fromHtml($sBody)->inlineCss($sCustomStyles)->getDomDocument(); HtmlPruner::fromDomDocument($oDomDocument)->removeElementsWithDisplayNone(); @@ -428,22 +400,22 @@ class EMail $this->m_aData['body'] = array('body' => $sBody, 'mimeType' => $sMimeType); // We don't want these modifications in m_aData['body'], otherwise it'll ruin asynchronous mail as they go through this method twice - if ($sMimeType === Mime::TYPE_HTML){ + if ($sMimeType === Mime::TYPE_HTML) { $aAdditionalParts = $this->EmbedInlineImages($sBody); - } - + } + // Add body content to as a new part $oNewPart = new Part($sBody); $oNewPart->encoding = Mime::ENCODING_8BIT; $oNewPart->type = $sMimeType; $oBody->addPart($oNewPart); - + // Add additional images as new body parts foreach ($aAdditionalParts as $oAdditionalPart) { $oBody->addPart($oAdditionalPart); } - if($oBody->isMultiPart()){ + if ($oBody->isMultiPart()) { $oContentTypeHeader = $this->m_oMessage->getHeaders(); foreach ($oContentTypeHeader as $oHeader) { if (!$oHeader instanceof ContentType) { @@ -455,12 +427,13 @@ class EMail break; } } - + $this->m_oMessage->setBody($oBody); } /** * Add a new part to the existing body + * * @param $sText * @param string $sMimeType * @@ -468,8 +441,7 @@ class EMail */ public function AddPart($sText, string $sMimeType = Mime::TYPE_HTML) { - if (!array_key_exists('parts', $this->m_aData)) - { + if (!array_key_exists('parts', $this->m_aData)) { $this->m_aData['parts'] = array(); } $this->m_aData['parts'][] = array('text' => $sText, 'mimeType' => $sMimeType); @@ -483,7 +455,7 @@ class EMail { $oBody = $this->m_oMessage->getBody(); - if(!$oBody->isMultiPart()){ + if (!$oBody->isMultiPart()) { $multipart_content = new Part($oBody->generateMessage()); $multipart_content->setType($oBody->getParts()[0]->getType()); $multipart_content->setBoundary($oBody->getMime()->boundary()); @@ -492,8 +464,7 @@ class EMail $oBody->addPart($multipart_content); } - if (!array_key_exists('attachments', $this->m_aData)) - { + if (!array_key_exists('attachments', $this->m_aData)) { $this->m_aData['attachments'] = array(); } $this->m_aData['attachments'][] = array('data' => base64_encode($data), 'filename' => $sFileName, 'mimeType' => $sMimeType); @@ -506,7 +477,7 @@ class EMail $oBody->addPart($oNewAttachment); - if($oBody->isMultiPart()){ + if ($oBody->isMultiPart()) { $oContentTypeHeader = $this->m_oMessage->getHeaders(); foreach ($oContentTypeHeader as $oHeader) { if (!$oHeader instanceof ContentType) { @@ -535,27 +506,25 @@ class EMail /** * Helper to transform and sanitize addresses - * - get rid of empty addresses - */ + * - get rid of empty addresses + */ protected function AddressStringToArray($sAddressCSVList) { $aAddresses = array(); - foreach(explode(',', $sAddressCSVList) as $sAddress) - { + foreach (explode(',', $sAddressCSVList) as $sAddress) { $sAddress = trim($sAddress); - if (strlen($sAddress) > 0) - { + if (strlen($sAddress) > 0) { $aAddresses[] = $sAddress; } } + return $aAddresses; } - + public function SetRecipientTO($sAddress) { $this->m_aData['to'] = $sAddress; - if (!empty($sAddress)) - { + if (!empty($sAddress)) { $aAddresses = $this->AddressStringToArray($sAddress); $this->m_oMessage->setTo($aAddresses); } @@ -564,32 +533,25 @@ class EMail public function GetRecipientTO($bAsString = false) { $aRes = $this->m_oMessage->getTo(); - if ($aRes === null || $aRes->count() === 0) - { + if ($aRes === null || $aRes->count() === 0) { // There is no "To" header field $aRes = array(); } - if ($bAsString) - { + if ($bAsString) { $aStrings = array(); - foreach ($aRes as $oEmail) - { + foreach ($aRes as $oEmail) { $sName = $oEmail->getName(); $sEmail = $oEmail->getEmail(); - if (is_null($sName)) - { + if (is_null($sName)) { $aStrings[] = $sEmail; - } - else - { + } else { $sName = str_replace(array('<', '>'), '', $sName); $aStrings[] = "$sName <$sEmail>"; } } + return implode(', ', $aStrings); - } - else - { + } else { return $aRes; } } @@ -597,8 +559,7 @@ class EMail public function SetRecipientCC($sAddress) { $this->m_aData['cc'] = $sAddress; - if (!empty($sAddress)) - { + if (!empty($sAddress)) { $aAddresses = $this->AddressStringToArray($sAddress); $this->m_oMessage->setCc($aAddresses); } @@ -607,8 +568,7 @@ class EMail public function SetRecipientBCC($sAddress) { $this->m_aData['bcc'] = $sAddress; - if (!empty($sAddress)) - { + if (!empty($sAddress)) { $aAddresses = $this->AddressStringToArray($sAddress); $this->m_oMessage->setBcc($aAddresses); } @@ -617,12 +577,9 @@ class EMail public function SetRecipientFrom($sAddress, $sLabel = '') { $this->m_aData['from'] = array('address' => $sAddress, 'label' => $sLabel); - if ($sLabel != '') - { - $this->m_oMessage->setFrom(array($sAddress => $sLabel)); - } - else if (!empty($sAddress)) - { + if ($sLabel != '') { + $this->m_oMessage->setFrom(array($sAddress => $sLabel)); + } else if (!empty($sAddress)) { $this->m_oMessage->setFrom($sAddress); } } @@ -630,12 +587,9 @@ class EMail public function SetRecipientReplyTo($sAddress, $sLabel = '') { $this->m_aData['reply_to'] = array('address' => $sAddress, 'label' => $sLabel); - if ($sLabel != '') - { + if ($sLabel != '') { $this->m_oMessage->setReplyTo(array($sAddress => $sLabel)); - } - else if (!empty($sAddress)) - { + } else if (!empty($sAddress)) { $this->m_oMessage->setReplyTo($sAddress); } } diff --git a/sources/Core/Email/EmailSwiftMailer.php b/sources/Core/Email/EmailSwiftMailer.php index b0454a049..1fa7f6492 100644 --- a/sources/Core/Email/EmailSwiftMailer.php +++ b/sources/Core/Email/EmailSwiftMailer.php @@ -30,18 +30,8 @@ use Pelago\Emogrifier\HtmlProcessor\HtmlPruner; Swift_Preferences::getInstance()->setCharset('UTF-8'); - -define ('EMAIL_SEND_OK', 0); -define ('EMAIL_SEND_PENDING', 1); -define ('EMAIL_SEND_ERROR', 2); - class EmailSwiftMailer { - // Serialization formats - const ORIGINAL_FORMAT = 1; // Original format, consisting in serializing the whole object, inculding the Swift Mailer's object. - // Did not work with attachements since their binary representation cannot be stored as a valid UTF-8 string - const FORMAT_V2 = 2; // New format, only the raw data are serialized (base64 encoded if needed) - protected static $m_oConfig = null; protected $m_aData; // For storing data to serialize @@ -155,6 +145,11 @@ class EmailSwiftMailer return EMAIL_SEND_PENDING; } + public static function GetMailer() + { + return new EmailSwiftMailer(); + } + protected function SendSynchronous(&$aIssues, $oLog = null) { // If the body of the message is in HTML, embed all images based on attachments diff --git a/sources/application/TwigBase/Controller/Controller.php b/sources/application/TwigBase/Controller/Controller.php index aea709b07..e8618699d 100644 --- a/sources/application/TwigBase/Controller/Controller.php +++ b/sources/application/TwigBase/Controller/Controller.php @@ -71,14 +71,14 @@ abstract class Controller * @param string $sViewPath Path of the twig files * @param string $sModuleName name of the module (or 'core' if not a module) */ - public function __construct($sViewPath, $sModuleName = 'core') + public function __construct($sViewPath, $sModuleName = 'core', $aAdditionalPaths = []) { $this->m_aLinkedScripts = array(); $this->m_aLinkedStylesheets = array(); $this->m_aSaas = array(); $this->m_aAjaxTabs = array(); $this->m_aDefaultParams = array(); - $this->SetViewPath($sViewPath); + $this->SetViewPath($sViewPath, $aAdditionalPaths); $this->SetModuleName($sModuleName); if ($sModuleName != 'core') { @@ -116,9 +116,9 @@ abstract class Controller * * @param string $sViewPath */ - public function SetViewPath($sViewPath) + public function SetViewPath($sViewPath, $aAdditionalPaths = []) { - $oTwig = TwigHelper::GetTwigEnvironment($sViewPath); + $oTwig = TwigHelper::GetTwigEnvironment($sViewPath, $aAdditionalPaths); $this->m_oTwig = $oTwig; } diff --git a/sources/application/TwigBase/Twig/TwigHelper.php b/sources/application/TwigBase/Twig/TwigHelper.php index 99253e701..efa3d5339 100644 --- a/sources/application/TwigBase/Twig/TwigHelper.php +++ b/sources/application/TwigBase/Twig/TwigHelper.php @@ -17,9 +17,13 @@ use WebPage; class TwigHelper { - public static function GetTwigEnvironment($sViewPath) + public static function GetTwigEnvironment($sViewPath, $aAdditionalPaths = []) { $oLoader = new Twig_Loader_Filesystem($sViewPath); + foreach ($aAdditionalPaths as $sAdditionalPath) { + $oLoader->addPath($sAdditionalPath); + } + $oTwig = new Twig_Environment($oLoader); Extension::RegisterTwigExtensions($oTwig); $sLocalPath = utils::LocalPath($sViewPath); diff --git a/templates/pages/backoffice/oauth/DisplayConfig.html.twig b/templates/pages/backoffice/oauth/DisplayConfig.html.twig index 185943da0..1fcf8b8b3 100644 --- a/templates/pages/backoffice/oauth/DisplayConfig.html.twig +++ b/templates/pages/backoffice/oauth/DisplayConfig.html.twig @@ -1,7 +1,7 @@ {# @copyright Copyright (C) 2010-2022 Combodo SARL #} {# @license http://opensource.org/licenses/AGPL-3.0 #} -
+
{{ 'UI:OAuth:Wizard:ResultConf:Panel:Title'|dict_s }}

{{ 'UI:OAuth:Wizard:ResultConf:Panel:Description'|dict_s }}