From 729e4a126b84f52959473ce04fc7165e8c680dcc Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Wed, 27 Oct 2021 23:26:47 +0300 Subject: [PATCH 01/16] Create SMTPServer.php --- tests/entity/mail/SMTPServer.php | 345 +++++++++++++++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 tests/entity/mail/SMTPServer.php diff --git a/tests/entity/mail/SMTPServer.php b/tests/entity/mail/SMTPServer.php new file mode 100644 index 000000000..0dcf9b23b --- /dev/null +++ b/tests/entity/mail/SMTPServer.php @@ -0,0 +1,345 @@ +port = $port; + $this->host = $serverAddress; + $this->serverOptions = []; + $this->timeout = 5; + } + /** + * Returns the last command which was sent to SMTP server. + * + * @return string The last command which was sent to SMTP server. + * + * @since 1.0 + */ + public function getLastSentCommand() { + return $this->lastCommand; + } + /** + * Returns last response code that was sent by SMTP server after executing + * specific command. + * + * @return int The last response code that was sent by SMTP server after executing + * specific command. Default return value is 0. + * + * @since 1.0 + */ + public function getLastResponseCode() { + return $this->lastResponseCode; + } + /** + * Returns the last response message which was sent by the server. + * + * @return string The last response message after executing some command. Default + * value is empty string. + * + * @since 1.0 + */ + public function getLastLogMessage() { + return $this->lastResponse; + } + public function getServerOptions() { + return $this->serverOptions; + } + private function _parseHelloResponse($response) { + $split = explode(self::NL, $response); + foreach ($split as $part) { + $xPart = substr($part, 4); + $this->serverOptions[] = $xPart; + } + } + private function _tryConnect($protocol) { + $host = $this->getHost(); + $portNum = $this->getPort(); + $conn = null; + $err = 0; + $errStr = ''; + $timeout = $this->getTimeout(); + + if (function_exists('stream_socket_client')) { + $context = stream_context_create([ + 'ssl' => [ + 'verify_peer' => false, + 'verify_peer_name' => false, + 'allow_self_signed' => true, + + 'crypto_type' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER + ] + ]); + var_dump(STREAM_CRYPTO_METHOD_TLSv1_2_SERVER); + + $this->_log('Connect', 0, 'Trying to connect to the server using "stream_socket_client"...'); + $conn = stream_socket_client($protocol.$host.':'.$portNum, $err, $errStr, $timeout * 60, STREAM_CLIENT_CONNECT, $context); + } else { + $this->_log('Connect', 0, 'Trying to connect to the server using "fsockopen"...'); + $conn = fsockopen($protocol.$this->host, $portNum, $err, $errStr, $timeout * 60); + } + if (!is_resource($conn)) { + $this->_log('Connect', $err, 'Faild to connect: '.$errStr); + } + return $conn; + } + public function connect() { + $retVal = true; + + if (!$this->isConnected()) { + set_error_handler(function($errno, $errstr, $errfile, $errline) + { + echo 'ErrorNo: '.$errno."\n";; + echo 'ErrorLine: '.$errline."\n";; + echo 'ErrorStr: '.$errstr."\n";; + }); + $portNum = $this->getPort(); + $protocol = ''; + + if ($portNum == 465) { + $protocol = "ssl://"; + } else if ($portNum == 587) { + $protocol = "tls://"; + } + $err = 0; + $errStr = ''; + + $this->conn = $this->_tryConnect($protocol, $err, $errStr); + + if ($this->conn === false) { + $this->conn = $this->_tryConnect($protocol, $err, $errStr); + } + + set_error_handler(null); + + if (is_resource($this->conn)) { + $this->_log('-', 0, $this->read()); + if ($this->sendCommand('EHLO '.$this->host)) { + $retVal = true; + } + } else { + $retVal = false; + } + } + + return $retVal; + } + /** + * Read server response after sending a command to the server. + * @return string + * @since 1.0 + */ + public function read() { + $message = ''; + $lastComm = $this->getLastSentCommand(); + + while (!feof($this->conn)) { + $str = fgets($this->conn); + + if ($lastComm == 'EHLO' && strlen($message) != 0) { + $this->_addServerOption($str); + } + $message .= $str; + + if (!isset($str[3]) || (isset($str[3]) && $str[3] == ' ')) { + break; + } + } + $this->_setLastResponseCode($message); + + return $message; + } + /** + * Returns an array that contains log messages for different events or + * commands which was sent to the server. + * + * @return array The array will hold sub-associative arrays. Each array + * will have 3 indices, ' + */ + public function getLog() { + return $this->responseLog; + } + private function _addServerOption($str) { + $option = trim(substr($str, 3),"- ".self::NL); + $this->serverOptions[] = $option; + } + /** + * Sets the code that was the result of executing SMTP command. + * @param string $serverResponseMessage The last message which was sent by + * the server after executing specific command. + * @since 1.4.7 + */ + private function _setLastResponseCode($serverResponseMessage) { + $firstNum = $serverResponseMessage[0]; + $firstAsInt = intval($firstNum); + + if ($firstAsInt != 0) { + $secNum = $serverResponseMessage[1]; + $thirdNum = $serverResponseMessage[2]; + $this->lastResponseCode = intval($firstNum) * 100 + (intval($secNum * 10)) + (intval($thirdNum)); + } + } + private function _log($command, $code, $message) { + $this->responseLog[] = [ + 'command' => $command, + 'response-code' => $code, + 'response-message' => $message + ]; + } + /** + * Sends a command to the mail server. + * + * @param string $command Any SMTP command which is supported by the server. + * + * @return boolean The method will return always true if the command was + * sent. The only case that the method will return false is when it is not + * connected to the server. + * + * @since 1.0 + */ + public function sendCommand($command) { + $this->lastCommand = explode(' ', $command)[0]; + $logEntry = [ + 'command' => $command, + 'response-code' => 0, + 'response-message' => '' + ]; + + if ($this->lastResponseCode >= 400) { + throw new SMTPException('Unable to send SMTP commend "'.$command.'" due to ' + .'error code '.$this->lastResponseCode.' caused by last command. ' + .'Error message: "'.$this->lastResponse.'".'); + } + + if ($this->isConnected()) { + fwrite($this->conn, $command.self::NL); + $response = trim($this->read()); + $this->lastResponse = $response; + $logEntry['response-message'] = $response; + $logEntry['response-code'] = $this->getLastResponseCode(); + $this->responseLog[] = $logEntry; + + if (substr($command, 0, 4) == 'HELO' || substr($command, 0, 4) == 'EHLO') { + $this->_parseHelloResponse($response); + } + return true; + } else { + $this->responseLog[] = $logEntry; + + return false; + } + } + /** + * Sets the timeout time of the connection. + * + * @param int $val The value of timeout (in minutes). The timeout will be updated + * only if the connection is not yet established and the given value is grater + * than 0. + * + * @since 1.0 + */ + public function setTimeout($val) { + if ($val >= 1 && !$this->isConnected()) { + $this->timeout = $val; + } + } + /** + * + * @return type + * + * @since 1.0 + */ + public function getPort() { + return $this->port; + } + /** + * + * @return type + * + * @since 1.0 + */ + public function getHost() { + return $this->host; + } + /** + * Returns the time at which the connection will timeout if no response + * was received in minutes. + * + * @return int Timeout time in minutes. + * + * @since 1.0 + */ + public function getTimeout() { + return $this->timeout; + } + /** + * Checks if the connection is still open or is it closed. + * + * @return boolean true if the connection is open. + * + * @since 1.0 + */ + public function isConnected() { + return is_resource($this->conn); + } +} From 3e00094d85fe80affa6a1137afaac9d8cf462fd7 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Sat, 30 Oct 2021 15:51:42 +0300 Subject: [PATCH 02/16] Update SMTPServer.php --- tests/entity/mail/SMTPServer.php | 47 +++++++++++++++++++++++++------- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/tests/entity/mail/SMTPServer.php b/tests/entity/mail/SMTPServer.php index 0dcf9b23b..764b851cb 100644 --- a/tests/entity/mail/SMTPServer.php +++ b/tests/entity/mail/SMTPServer.php @@ -99,7 +99,7 @@ public function getLastResponseCode() { * * @since 1.0 */ - public function getLastLogMessage() { + public function getLastResponse() { return $this->lastResponse; } public function getServerOptions() { @@ -149,9 +149,9 @@ public function connect() { if (!$this->isConnected()) { set_error_handler(function($errno, $errstr, $errfile, $errline) { - echo 'ErrorNo: '.$errno."\n";; - echo 'ErrorLine: '.$errline."\n";; - echo 'ErrorStr: '.$errstr."\n";; + echo 'ErrorNo: '.$errno."\n"; + echo 'ErrorLine: '.$errline."\n"; + echo 'ErrorStr: '.$errstr."\n"; }); $portNum = $this->getPort(); $protocol = ''; @@ -167,15 +167,22 @@ public function connect() { $this->conn = $this->_tryConnect($protocol, $err, $errStr); if ($this->conn === false) { - $this->conn = $this->_tryConnect($protocol, $err, $errStr); + $this->conn = $this->_tryConnect('', $err, $errStr); } set_error_handler(null); if (is_resource($this->conn)) { $this->_log('-', 0, $this->read()); - if ($this->sendCommand('EHLO '.$this->host)) { - $retVal = true; + if ($this->sendHello()) { + if (in_array('STARTTLS', $this->getServerOptions())) { + if ($this->_switchToTls()) { + $this->sendHello(); + $retVal = true; + } + } else { + $retVal = true; + } } } else { $retVal = false; @@ -279,9 +286,6 @@ public function sendCommand($command) { $logEntry['response-code'] = $this->getLastResponseCode(); $this->responseLog[] = $logEntry; - if (substr($command, 0, 4) == 'HELO' || substr($command, 0, 4) == 'EHLO') { - $this->_parseHelloResponse($response); - } return true; } else { $this->responseLog[] = $logEntry; @@ -289,6 +293,29 @@ public function sendCommand($command) { return false; } } + private function _switchToTls() { + $crypMethod = STREAM_CRYPTO_METHOD_TLS_CLIENT + | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT + | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; + + + set_error_handler(function () {}); + $success = stream_socket_enable_crypto( + $this->conn, + true, + $crypMethod + ); + restore_error_handler(); + + return $success === true; + } + public function sendHello() { + if($this->sendCommand('EHLO '.$this->getHost())) { + $this->_parseHelloResponse($this->getLastResponse()); + return true; + } + return false; + } /** * Sets the timeout time of the connection. * From 8ea1422f5dc97599e563206ce83625e8e3ff4cff Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Sat, 30 Oct 2021 15:51:48 +0300 Subject: [PATCH 03/16] Create TestSMTPServer.php --- tests/entity/mail/TestSMTPServer.php | 38 ++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 tests/entity/mail/TestSMTPServer.php diff --git a/tests/entity/mail/TestSMTPServer.php b/tests/entity/mail/TestSMTPServer.php new file mode 100644 index 000000000..5dfc4667a --- /dev/null +++ b/tests/entity/mail/TestSMTPServer.php @@ -0,0 +1,38 @@ +assertEquals('smtp.gmail.com', $server->getHost()); + $this->assertEquals(465, $server->getPort()); + + $this->assertTrue($server->connect()); + } + /** + * @test + */ + public function test01() { + $server = new SMTPServer('smtp.outlook.com', 587); + $this->assertEquals('smtp.outlook.com', $server->getHost()); + $this->assertEquals(587, $server->getPort()); + + $this->assertTrue($server->connect()); + } +} From b1fffc463e8432846be5f20ff621f08168799fcf Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Sun, 31 Oct 2021 17:44:57 +0300 Subject: [PATCH 04/16] Update SMTPServer.php --- tests/entity/mail/SMTPServer.php | 145 ++++++++++++++++++++++++------- 1 file changed, 112 insertions(+), 33 deletions(-) diff --git a/tests/entity/mail/SMTPServer.php b/tests/entity/mail/SMTPServer.php index 764b851cb..e269e86dd 100644 --- a/tests/entity/mail/SMTPServer.php +++ b/tests/entity/mail/SMTPServer.php @@ -3,7 +3,7 @@ namespace webfiori\tests\entity\mail; /** - * Description of SMTPServer + * A class which can be used to connect to SMTP server and execute commands on it. * * @author Ibrahim * @@ -60,15 +60,55 @@ class SMTPServer { private $responseLog; /** * Connection timeout (in minutes) + * * @var int */ private $timeout; + /** + * Initiates new instance of the class. + * + * @param string $serverAddress SMTP Server address such as 'smtp.example.com'. + * + * @param string $port SMTP server port such as 25, 465 or 587. + */ public function __construct($serverAddress, $port) { $this->port = $port; $this->host = $serverAddress; $this->serverOptions = []; $this->timeout = 5; } + /** + * Use plain authorization method to log in the user to SMTP server. + * + * This method will attempt to establish a connection to SMTP server if + * the method 'SMTPServer::connect()' is called. + * + * @param string $username The username of SMTP server user. + * + * @param string $pass The password of the user. + * + * @return boolean If the user is authenticated successfully, the method + * will return true. Other than that, the method will return false. + * + * @since 1.0 + */ + public function authLogin($username, $pass) { + if (!$this->isConnected()) { + $this->connect(); + + if (!$this->isConnected()) { + return false; + } + } + $this->sendCommand('AUTH LOGIN'); + $this->sendCommand(base64_encode($username)); + $this->sendCommand(base64_encode($pass)); + + if ($this->getLastResponseCode() == 535) { + return false; + } + return true; + } /** * Returns the last command which was sent to SMTP server. * @@ -102,14 +142,31 @@ public function getLastResponseCode() { public function getLastResponse() { return $this->lastResponse; } + /** + * Returns an array that contains server supported commands. + * + * The method will only be able to get the options after sending the + * command 'EHLO' to the server. The array will be empty if not + * connected to SMTP server. + * + * @return array An array that holds supported SMTP server options. + * + * @since 1.0 + */ public function getServerOptions() { return $this->serverOptions; } private function _parseHelloResponse($response) { $split = explode(self::NL, $response); + $index = 0; + $this->serverOptions = []; foreach ($split as $part) { - $xPart = substr($part, 4); - $this->serverOptions[] = $xPart; + //Index 0 will usually hold server address + if ($index != 0) { + $xPart = substr($part, 4); + $this->serverOptions[] = $xPart; + } + $index++; } } private function _tryConnect($protocol) { @@ -130,7 +187,7 @@ private function _tryConnect($protocol) { 'crypto_type' => STREAM_CRYPTO_METHOD_TLSv1_2_SERVER ] ]); - var_dump(STREAM_CRYPTO_METHOD_TLSv1_2_SERVER); + $this->_log('Connect', 0, 'Trying to connect to the server using "stream_socket_client"...'); $conn = stream_socket_client($protocol.$host.':'.$portNum, $err, $errStr, $timeout * 60, STREAM_CLIENT_CONNECT, $context); @@ -143,15 +200,21 @@ private function _tryConnect($protocol) { } return $conn; } + /** + * Connects to SMTP server. + * + * @return boolean If the connection was established and the 'EHLO' command + * was successfully sent, the method will return true. Other than that, the + * method will return false. + * + * @since 1.0 + */ public function connect() { $retVal = true; if (!$this->isConnected()) { set_error_handler(function($errno, $errstr, $errfile, $errline) { - echo 'ErrorNo: '.$errno."\n"; - echo 'ErrorLine: '.$errline."\n"; - echo 'ErrorStr: '.$errstr."\n"; }); $portNum = $this->getPort(); $protocol = ''; @@ -179,6 +242,8 @@ public function connect() { if ($this->_switchToTls()) { $this->sendHello(); $retVal = true; + } else { + $retVal = false; } } else { $retVal = true; @@ -193,19 +258,17 @@ public function connect() { } /** * Read server response after sending a command to the server. + * * @return string + * * @since 1.0 */ public function read() { $message = ''; - $lastComm = $this->getLastSentCommand(); while (!feof($this->conn)) { $str = fgets($this->conn); - if ($lastComm == 'EHLO' && strlen($message) != 0) { - $this->_addServerOption($str); - } $message .= $str; if (!isset($str[3]) || (isset($str[3]) && $str[3] == ' ')) { @@ -221,20 +284,20 @@ public function read() { * commands which was sent to the server. * * @return array The array will hold sub-associative arrays. Each array - * will have 3 indices, ' + * will have 3 indices, 'command', 'response-code' and 'response-message' + * + * @since 1.0 */ public function getLog() { return $this->responseLog; } - private function _addServerOption($str) { - $option = trim(substr($str, 3),"- ".self::NL); - $this->serverOptions[] = $option; - } /** * Sets the code that was the result of executing SMTP command. + * * @param string $serverResponseMessage The last message which was sent by * the server after executing specific command. - * @since 1.4.7 + * + * @since 1.0 */ private function _setLastResponseCode($serverResponseMessage) { $firstNum = $serverResponseMessage[0]; @@ -266,11 +329,6 @@ private function _log($command, $code, $message) { */ public function sendCommand($command) { $this->lastCommand = explode(' ', $command)[0]; - $logEntry = [ - 'command' => $command, - 'response-code' => 0, - 'response-message' => '' - ]; if ($this->lastResponseCode >= 400) { throw new SMTPException('Unable to send SMTP commend "'.$command.'" due to ' @@ -282,33 +340,52 @@ public function sendCommand($command) { fwrite($this->conn, $command.self::NL); $response = trim($this->read()); $this->lastResponse = $response; - $logEntry['response-message'] = $response; - $logEntry['response-code'] = $this->getLastResponseCode(); - $this->responseLog[] = $logEntry; + $this->_log($command, $this->getLastResponseCode(), $response); return true; } else { - $this->responseLog[] = $logEntry; + $this->_log($command, 0, ''); return false; } } private function _switchToTls() { - $crypMethod = STREAM_CRYPTO_METHOD_TLS_CLIENT - | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT - | STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; + $this->sendCommand('STARTTLS'); + $cryptoMethod = STREAM_CRYPTO_METHOD_TLS_CLIENT; + + if (defined('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT')) { + $cryptoMethod |= STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT; + $cryptoMethod |= STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT; + } - set_error_handler(function () {}); + set_error_handler(function ($errno, $errstr, $errfile, $errline) + { + echo 'ErrorNo: '.$errno."\n"; + echo 'ErrorLine: '.$errline."\n"; + echo 'ErrorStr: '.$errstr."\n"; + + }); $success = stream_socket_enable_crypto( $this->conn, true, - $crypMethod + $cryptoMethod ); restore_error_handler(); return $success === true; } + /** + * Sends 'EHLO' command to SMTP server. + * + * The developer does not have to call this method manually as its + * called when connecting to SMTP server. + * + * @return boolean If the command was sent successfully, the method will + * return true. Other than that, the method will return false. + * + * @since 1.0 + */ public function sendHello() { if($this->sendCommand('EHLO '.$this->getHost())) { $this->_parseHelloResponse($this->getLastResponse()); @@ -331,8 +408,9 @@ public function setTimeout($val) { } } /** + * Returns SMTP server port number. * - * @return type + * @return int Common values are : 25, 465 (SSL) and 586 (TLS). * * @since 1.0 */ @@ -340,8 +418,9 @@ public function getPort() { return $this->port; } /** + * Returns SMTP server host address. * - * @return type + * @return string A string such as 'smtp.example.com'. * * @since 1.0 */ From 64d4f71cd8561d5d656c6515979d579ccdb3dd69 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Sun, 31 Oct 2021 22:36:11 +0300 Subject: [PATCH 05/16] Update SMTPServer.php --- {tests/entity => webfiori/framework}/mail/SMTPServer.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename {tests/entity => webfiori/framework}/mail/SMTPServer.php (99%) diff --git a/tests/entity/mail/SMTPServer.php b/webfiori/framework/mail/SMTPServer.php similarity index 99% rename from tests/entity/mail/SMTPServer.php rename to webfiori/framework/mail/SMTPServer.php index e269e86dd..789300530 100644 --- a/tests/entity/mail/SMTPServer.php +++ b/webfiori/framework/mail/SMTPServer.php @@ -1,6 +1,6 @@ Date: Sun, 31 Oct 2021 22:36:17 +0300 Subject: [PATCH 06/16] Update EmailMessage.php --- webfiori/framework/mail/EmailMessage.php | 402 ++++++++++++++++------- 1 file changed, 291 insertions(+), 111 deletions(-) diff --git a/webfiori/framework/mail/EmailMessage.php b/webfiori/framework/mail/EmailMessage.php index b52abc7b6..b20ed57d2 100644 --- a/webfiori/framework/mail/EmailMessage.php +++ b/webfiori/framework/mail/EmailMessage.php @@ -25,6 +25,7 @@ namespace webfiori\framework\mail; use webfiori\framework\ConfigController; +use webfiori\framework\mail\SMTPServer; use webfiori\framework\exceptions\SMTPException; use webfiori\framework\File; use webfiori\framework\WebFioriApp; @@ -37,20 +38,121 @@ * @version 1.0.5 */ class EmailMessage { + /** + * A boundary variable used to separate email message parts. + * + * @var string + * + * @since 2.0 + */ + private $boundry; + /** + * + * @var SMTPServer|null + * + * @since 2.0 + */ + private $smtpServer; + /** + * The subject of the email message. + * + * @var string + * + * @since 2.0 + */ + private $subject; + /** + * An array that contains an objects of type 'File' or + * file path. + * + * @var array + * + * @since 2.0 + */ + private $attachments; + /** + * SMTP account that will be used to send the message. + * + * @var SMTPAccount + * + * @since 1.0 + */ + private $smtpAcc; + /** + * A constant that colds the possible values for the header 'Priority'. + * + * @see https://tools.ietf.org/html/rfc4021#page-33 + * + * @since 2.0 + */ + const PRIORITIES = [ + -1 => 'non-urgent', + 0 => 'normal', + 1 => 'urgent' + ]; + /** + * + * @var array + * + * @since 2.0 + */ + private $receiversArr; + private $inReplyTo; + /** + * Sets the subject of the message. + * + * @param string $subject Email subject. + * + * @since 2.0 + */ + public function setSubject($subject) { + $trimmed = $this->_trimControlChars($subject); + + if (strlen($trimmed) > 0) { + $this->subject = $trimmed; + } + } + /** + * Returns the subject of the email. + * + * @return string The subject of the email. Default return value is + * 'Hello From WebFiori Framework'. + * + * @since 2.0 + */ + public function getSubject() { + return $this->subject; + } + /** + * Sets the priority of the message. + * + * @param int $priority The priority of the message. -1 for non-urgent, 0 + * for normal and 1 for urgent. If the passed value is greater than 1, + * then 1 will be used. If the passed value is less than -1, then -1 is + * used. Other than that, 0 will be used. + * + * @since 2.0 + */ + public function setPriority($priority) { + $asInt = intval($priority); + + if ($asInt <= -1) { + $this->priority = -1; + } else if ($asInt >= 1) { + $this->priority = 1; + } else { + $this->priority = 0; + } + } /** * * @var HTMLDoc + * * @since 1.0 */ private $asHtml; private $log; - /** - * - * @var SocketMailer - * @since 1.0 - */ - private $socketMailer; /** * Creates new instance of the class. * @param type $sendAccountName @@ -60,76 +162,118 @@ class EmailMessage { */ public function __construct($sendAccountName = '') { $this->log = []; - + $this->setPriority(0); + $this->boundry = hash('sha256', date(DATE_ISO8601)); + $this->receiversArr = [ + 'cc' => [], + 'bcc' => [], + 'to' => [] + ]; + $this->attachments = []; + $this->inReplyTo = []; + if (class_exists(APP_DIR_NAME.'\AppConfig')) { $acc = WebFioriApp::getAppConfig()->getAccount($sendAccountName); if ($acc instanceof SMTPAccount) { - $this->socketMailer = ConfigController::get()->getSocketMailer($acc); - - if ($this->socketMailer == ConfigController::INV_CREDENTIALS) { - throw new SMTPException('The account "'.$sendAccountName.'" has invalid credintials.'); - } else { - if ($this->socketMailer == ConfigController::INV_HOST_OR_PORT) { - throw new SMTPException('The account "'.$sendAccountName.'" has invalid host or port number. Port: '.$acc->getPort().', Host: '.$acc->getServerAddress().'.'); - } else { - $this->asHtml = new HTMLDoc(); - $this->asHtml->getHeadNode()->addMeta('charset', 'UTF-8'); - - return; - } - } + $this->smtpAcc = $acc; } throw new SMTPException('No SMTP account was found which has the name "'.$sendAccountName.'".'); } throw new SMTPException('Class "'.APP_DIR_NAME.'\\AppConfig" not found.'); } /** - * Adds new receiver address to the list of message receivers. + * Adds new receiver address to the list of 'to' receivers. * - * @param string $name The name of the email receiver (such as 'Ibrahim'). + * @param string $address The email address of the receiver (such as 'example@example.com'). * - * @param string $email The email address of the receiver (such as 'example@example.com'). + * @param string $name An optional receiver name. If not provided, the + * email address is used as name. * - * @param boolean $isCC If set to true, the receiver will receive - * a carbon copy of the message (CC). + * @return boolean If the address is added, the method will return + * true. False otherwise. * - * @param boolean $isBcc If set to true, the receiver will receive - * a blind carbon copy of the message (Bcc). + * @since 2.0 + */ + public function addTo($address, $name = null) { + return $this->_addAddress($address, $name, 'to'); + } + /** + * Adds a file as email attachment. * - * @since 1.0.4 + * @param File|string $fileObjOrFilePath An object of type 'File'. This also can + * be the absolute path to a file in the file system. + * + * @return boolean If the file is added, the method will return true. + * Other than that, the method will return false. + * + * @since 2.0 */ - public function addReceiver($name,$email,$isCC = false,$isBcc = false) { - $this->_getSocketMailer()->addReceiver($name, $email, $isCC, $isBcc); + public function addAttachment($fileObjOrFilePath) { + $retVal = false; + + $type = gettype($fileObjOrFilePath); + + if ($type == 'string') { + if (file_exists($fileObjOrFilePath)) { + $this->attachments[] = $fileObjOrFilePath; + $retVal = true; + } + } else { + if (class_exists('webfiori\framework\File') && $fileObjOrFilePath instanceof File + && (file_exists($fileObjOrFilePath->getAbsolutePath()) || file_exists(str_replace('\\', '/', $fileObjOrFilePath->getAbsolutePath())) || $fileObjOrFilePath->getRawData() !== null)) { + $this->attachments[] = $fileObjOrFilePath; + $retVal = true; + } + } + + return $retVal; } /** - * Adds a file to the email message as an attachment. + * Adds new receiver address to the list of 'cc' receivers. * - * @param File $file The file that will be added. It will be added only if the file - * exist in the path or the raw data of the file is set. + * @param string $address The email address of the receiver (such as 'example@example.com'). * - * @since 1.0 + * @param string $name An optional receiver name. If not provided, the + * email address is used as name. + * + * @return boolean If the address is added, the method will return + * true. False otherwise. + * + * @since 2.0 */ - public function attach($file) { - $this->_getSocketMailer()->addAttachment($file); + public function addCC($address, $name = null) { + return $this->_addAddress($address, $name, 'cc'); } /** - * Sets or returns the HTML document that is associated with the email - * message. + * Adds new receiver address to the list of 'bcc' receivers. * - * @param HTMLDoc $new If it is not null, the HTML document - * that is associated with the message will be set to the given one. + * @param string $address The email address of the receiver (such as 'example@example.com'). * - * @return HTMLDoc The document that is associated with the email message. + * @param string $name An optional receiver name. If not provided, the + * email address is used as name. * - * @since 1.0 + * @return boolean If the address is added, the method will return + * true. False otherwise. + * + * @since 2.0 */ - public function document($new = null) { - if ($new != null) { - $this->_setDocument($new); + public function addBCC($address, $name = null) { + return $this->_addAddress($address, $name, 'bcc'); + } + private function _addAddress($address, $name, $type) { + $nameTrimmed = $this->_trimControlChars(str_replace('<', '', str_replace('>', '', $name))); + $addressTrimmed = $this->_trimControlChars(str_replace('<', '', str_replace('>', '', $address))); + + if (strlen($nameTrimmed) == 0) { + $nameTrimmed = $addressTrimmed; + } + if (strlen($addressTrimmed) != 0 && in_array($type, ['cc', 'bcc', 'to'])) { + $this->receiversArr[$type][$addressTrimmed] = $nameTrimmed; + return true; } - return $this->getDocument(); + return false; } /** * Returns an associative array that contains the names and the addresses @@ -139,10 +283,20 @@ public function document($new = null) { * the value of each index will contain the name of the receiver. * * @return array An array that contains receivers information. + * * @since 1.0.2 */ public function getBCC() { - return $this->_getSocketMailer()->getBCC(); + return $this->receiversArr['bcc']; + } + private function _getReceiversStr($type) { + $arr = []; + + foreach ($this->receiversArr[$type] as $address => $name) { + array_push($arr, '=?UTF-8?B?'.base64_encode($name).'?='.' <'.$address.'>'); + } + + return implode(',', $arr); } /** * Returns a string that contains the names and the addresses @@ -156,7 +310,7 @@ public function getBCC() { * @since 1.0.3 */ public function getBCCStr() { - $this->_getSocketMailer()->getBCCStr(); + $this->_getReceiversStr('bcc'); } /** * Returns an associative array that contains the names and the addresses @@ -170,7 +324,7 @@ public function getBCCStr() { * @since 1.0.2 */ public function getCC() { - $this->_getSocketMailer()->getCC(); + $this->receiversArr['cc']; } /** * Returns a string that contains the names and the addresses @@ -184,7 +338,7 @@ public function getCC() { * @since 1.0.3 */ public function getCCStr() { - $this->_getSocketMailer()->getCCStr(); + $this->_getReceiversStr('cc'); } /** * Returns a child node given its ID. @@ -231,8 +385,8 @@ public function getLog() { * * @since 1.0.2 */ - public function getReceivers() { - return $this->_getSocketMailer()->getReceivers(); + public function getTo() { + return $this->receiversArr['to']; } /** * Returns a string that contains the names and the addresses @@ -245,25 +399,8 @@ public function getReceivers() { * * @since 1.0.3 */ - public function getReceiversStr() { - return $this->_getSocketMailer()->getReceiversStr(); - } - /** - * Sets or gets the importance level of email message. - * - * @param int $imp The importance level of the message. -1 for not urgent, 0 - * for normal and 1 for urgent. - * - * @return int The importance level of the message. - * - * @since 1.0.1 - */ - public function importance($imp = null) { - if ($imp !== null) { - $this->_getSocketMailer()->setPriority($imp); - } - - return $this->_getSocketMailer()->getPriority(); + public function getToStr() { + return $this->_getReceiversStr('to'); } /** * Adds a child node inside the body of a node given its ID. @@ -293,45 +430,92 @@ public function insert($node, $parentNodeId = null) { return $node; } } - /** - * Adds a child HTML node to the body of the message. - * - * @param HTMLNode $htmlNode An instance of 'HTMLNode'. - * - * @since 1.0 - */ - public function insertNode($htmlNode) { - $this->getDocument()->addChild($htmlNode); - } /** * Sends the message and set message instance to null. * * @since 1.0 */ public function send() { - $this->_sendMessage(); + $acc = $this->getSMTPAccount(); + $this->smtpServer = new SMTPServer($acc->getServerAddress(), $acc->getPort()); + + if ($this->smtpServer->authLogin($acc->getUsername(), $acc->getPassword())) { + $this->smtpServer->sendCommand('MAIL FROM: <'.$acc->getAddress().'>'); + $this->_receiversCommand('to'); + $this->_receiversCommand('cc'); + $this->_receiversCommand('bcc'); + $this->smtpServer->sendCommand('DATA'); + $importanceHeaderVal = $this->_priorityCommand(); + + $this->smtpServer->sendCommand('Content-Transfer-Encoding: quoted-printable'); + $this->smtpServer->sendCommand('Importance: '.$importanceHeaderVal); + $this->smtpServer->sendCommand('From: =?UTF-8?B?'. base64_encode($acc->getSenderName()).'?= <'.$acc->getSenderName().'>'); + $this->smtpServer->sendCommand('To: '.$this->getToStr()); + $this->smtpServer->sendCommand('CC: '.$this->getCCStr()); + $this->smtpServer->sendCommand('BCC: '.$this->getBCCStr()); + $this->smtpServer->sendCommand('Date:'.date('r (T)')); + $this->smtpServer->sendCommand('Subject:'.'=?UTF-8?B?'.base64_encode($this->getSubject()).'?='); + $this->smtpServer->sendCommand('MIME-Version: 1.0'); + $this->smtpServer->sendCommand('Content-Type: multipart/mixed; boundary="'.$this->boundry.'"'.self::NL); + $this->smtpServer->sendCommand('--'.$this->boundry); + $this->smtpServer->sendCommand('Content-Type: text/html; charset="UTF-8"'.self::NL); + $this->smtpServer->sendCommand($this->_trimControlChars($this->getDocument()->toHTML())); + $this->_appendAttachments(); + $this->smtpServer->sendCommand(self::NL.'.'); + $this->smtpServer->sendCommand('QUIT'); + + } else { + throw new SMTPException('Unable to login to SMTP server: '.$this->smtpServer->getLastResponse(), $this->smtpServer->getLastResponseCode()); + } } /** - * Sets the document at which the message will use. + * A method that is used to include email attachments. * - * @param HTMLDoc $doc An HTML document. - * - * @since 1.0.5 + * @since 1.3 */ - public function setDocument($doc) { - if ($doc instanceof HTMLDoc) { - $this->asHtml = $doc; + private function _appendAttachments() { + if (count($this->attachments) != 0) { + foreach ($this->attachments as $file) { + if ($file->getRawData() === null) { + $file->read(); + } + $content = $file->getRawData(); + $contentChunk = chunk_split(base64_encode($content)); + $this->smtpServer->sendCommand('--'.$this->boundry); + $this->smtpServer->sendCommand('Content-Type: '.$file->getFileMIMEType().'; name="'.$file->getName().'"'); + $this->smtpServer->sendCommand('Content-Transfer-Encoding: base64'); + $this->smtpServer->sendCommand('Content-Disposition: attachment; filename="'.$file->getName().'"'.self::NL); + $this->smtpServer->sendCommand($contentChunk); + } + $this->smtpServer->sendCommand('--'.$this->boundry.'--'); + } + } + private function _priorityCommand() { + $priorityAsInt = $this->getPriority(); + $priorityHeaderVal = self::PRIORITIES[$priorityAsInt]; + + if ($priorityAsInt == -1) { + $importanceHeaderVal = 'low'; + } else if ($priorityAsInt == 1) { + $importanceHeaderVal = 'High'; + } else { + $importanceHeaderVal = 'normal'; + } + $this->smtpServer->sendCommand('Priority: '.$priorityHeaderVal); + + return $importanceHeaderVal; + } + private function _receiversCommand($type) { + foreach ($this->receiversArr[$type] as $address => $name) { + $this->smtpServer->sendCommand('RCPT TO: <'.$address.'>'); } } /** - * Sets the subject of the email message. - * - * @param string $subject The subject of the email message. * - * @since 1.0 + * @return SMTPAccount */ - public function subject($subject) { - $this->_getSocketMailer()->setSubject($subject); + public function getSMTPAccount() { + return $this->smtpAcc; } /** * Adds a text node to the body of the message. @@ -343,21 +527,6 @@ public function subject($subject) { public function write($text) { $this->getDocument()->addChild(HTMLNode::createTextNode($text,false)); } - /** - * - * @return SocketMailer - * @since 1.0 - */ - private function &_getSocketMailer() { - return $this->socketMailer; - } - /** - * @since 1.0 - */ - private function _sendMessage() { - $this->socketMailer->write($this->asHtml->toHTML(), true); - $this->log = $this->socketMailer->getResponsesLog(); - } /** * Returns the document that is associated with the page. * @@ -368,4 +537,15 @@ private function _sendMessage() { private function getDocument() { return $this->asHtml; } + /** + * Removes control characters from the start and end of string in addition + * to white spaces. + * + * @param string $str The string that will be trimmed. + * + * @return string The string after its control characters trimmed. + */ + private function _trimControlChars($str) { + return trim($str, "\x00..\x20"); + } } From ba59d1b62ae57d601d9746eb0c593d9adbf42bb1 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Sun, 31 Oct 2021 22:48:10 +0300 Subject: [PATCH 07/16] Update EmailMessage.php --- webfiori/framework/mail/EmailMessage.php | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/webfiori/framework/mail/EmailMessage.php b/webfiori/framework/mail/EmailMessage.php index b20ed57d2..6b85e6167 100644 --- a/webfiori/framework/mail/EmailMessage.php +++ b/webfiori/framework/mail/EmailMessage.php @@ -26,6 +26,7 @@ use webfiori\framework\ConfigController; use webfiori\framework\mail\SMTPServer; +use webfiori\framework\mail\SMTPAccount; use webfiori\framework\exceptions\SMTPException; use webfiori\framework\File; use webfiori\framework\WebFioriApp; @@ -171,12 +172,14 @@ public function __construct($sendAccountName = '') { ]; $this->attachments = []; $this->inReplyTo = []; + $this->asHtml = new HTMLDoc(); if (class_exists(APP_DIR_NAME.'\AppConfig')) { $acc = WebFioriApp::getAppConfig()->getAccount($sendAccountName); if ($acc instanceof SMTPAccount) { $this->smtpAcc = $acc; + return; } throw new SMTPException('No SMTP account was found which has the name "'.$sendAccountName.'".'); } @@ -490,6 +493,17 @@ private function _appendAttachments() { $this->smtpServer->sendCommand('--'.$this->boundry.'--'); } } + /** + * Returns the priority of the message. + * + * @return int The priority of the message. -1 for non-urgent, 0 + * for normal and 1 for urgent. Default value is 0. + * + * @since 2.0 + */ + public function getPriority() { + return $this->priority; + } private function _priorityCommand() { $priorityAsInt = $this->getPriority(); $priorityHeaderVal = self::PRIORITIES[$priorityAsInt]; From 65b5c81b9b1e762905c15c87f8403cebdf239517 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Mon, 1 Nov 2021 23:54:42 +0300 Subject: [PATCH 08/16] Update AppConfig.php --- app/AppConfig.php | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/app/AppConfig.php b/app/AppConfig.php index b289486d2..692461498 100644 --- a/app/AppConfig.php +++ b/app/AppConfig.php @@ -494,6 +494,24 @@ private function initSiteInfo() { */ private function initSmtpConnections() { $this->emailAccounts = [ + 'no-reply' => new SMTPAccount([ + 'port' => 587, + 'server-address' => 'outlook.office365.com', + 'user' => 'randomxyz@hotmail.com', + 'pass' => '???', + 'sender-name' => 'Ibrahim', + 'sender-address' => 'randomxyz@hotmail.com', + 'account-name' => 'no-reply' + ]), + 'no-reply2' => new SMTPAccount([ + 'port' => 465, + 'server-address' => 'smtp.gmail.com', + 'user' => 'randomxyz@gmail.com', + 'pass' => '???', + 'sender-name' => 'Ibrahim', + 'sender-address' => 'randomxyz@gmail.com', + 'account-name' => 'no-reply2' + ]) ]; } /** From d7ff509a8f1ce0edff3fbef3d7c6344b4dfc0a1b Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Mon, 1 Nov 2021 23:55:09 +0300 Subject: [PATCH 09/16] Update EmailMessageTest.php --- tests/entity/mail/EmailMessageTest.php | 147 ++++++++++++++++++------- 1 file changed, 105 insertions(+), 42 deletions(-) diff --git a/tests/entity/mail/EmailMessageTest.php b/tests/entity/mail/EmailMessageTest.php index 2ce48dccc..aafb004b5 100644 --- a/tests/entity/mail/EmailMessageTest.php +++ b/tests/entity/mail/EmailMessageTest.php @@ -14,68 +14,131 @@ class EmailMessageTest extends TestCase { /** * @test */ - public function test00() { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('No SMTP account was found which has the name "not exist".'); - $message = new EmailMessage('not exist'); + public function testAddReciver00() { + $sm = new EmailMessage('no-reply'); + $this->assertFalse($sm->addTo('', '')); + $this->assertFalse($sm->addTo('', 'Hello')); + $this->assertFalse($sm->addTo('', 'hello@web.com')); } /** * @test */ - public function test01() { - $smtp = new SMTPAccount(); - $smtp->setAccountName('smtp-acc-00'); - //$smtp->setServerAddress('mail.invalid.com'); - WebFioriApp::getAppConfig()->addAccount($smtp); - $this->expectException(\Exception::class); - $this->expectExceptionMessage('The account "smtp-acc-00" has invalid host or port number. Port: 465, Host: .'); - $message = new EmailMessage('smtp-acc-00'); + public function testAddReciver01() { + $sm = new EmailMessage('no-reply'); + $this->assertTrue($sm->addTo(' hello@>hello.com ', ' assertEquals('=?UTF-8?B?SGVsbG8=?= ',$sm->getToStr()); + $this->assertEquals('Hello',$sm->getTo()['hello@hello.com']); + $this->assertTrue($sm->addTo(' hello@>hello.com ', ' assertEquals('=?UTF-8?B?SGVsbG8y?= ',$sm->getToStr()); + $this->assertTrue($sm->addTo(' hello-9@>hello.com ', ' Hel>lo-9')); + $this->assertEquals('=?UTF-8?B?SGVsbG8y?= ,=?UTF-8?B?SGVsbG8tOQ==?= ',$sm->getToStr()); } /** * @test */ - public function test02() { - $this->expectException(\Exception::class); - $this->expectExceptionMessage('The account "smtp-acc-00" has invalid host or port number. Port: 255, Host: mail.programmingacademia.com.'); - $smtp = new SMTPAccount(); - $smtp->setPassword('iz1Iimu#z'); - $smtp->setAddress('test@programmingacademia.com'); - $smtp->setUsername('test@programmingacademia.com'); - $smtp->setServerAddress('mail.programmingacademia.com '); - $smtp->setPort(255); - $smtp->setAccountName('smtp-acc-00'); - WebFioriApp::getAppConfig()->addAccount($smtp); - $message = new EmailMessage('smtp-acc-00'); + public function testAddReciver02() { + $sm = new EmailMessage('no-reply'); + $this->assertTrue($sm->addCC(' hello@>hello.com ', ' assertEquals('=?UTF-8?B?SGVsbG8=?= ',$sm->getCCStr()); + $this->assertEquals('Hello',$sm->getCC()['hello@hello.com']); + $this->assertTrue($sm->addCC(' hello@>hello.com ', ' assertEquals('=?UTF-8?B?SGVsbG8y?= ',$sm->getCCStr()); + $this->assertTrue($sm->addCC(' hello-9@>hello.com ', ' Hel>lo-9 ')); + $this->assertEquals('=?UTF-8?B?SGVsbG8y?= ,=?UTF-8?B?SGVsbG8tOQ==?= ',$sm->getCCStr()); } /** * @test */ - public function test03() { + public function testAddReciver03() { + $sm = new EmailMessage('no-reply'); + $this->assertTrue($sm->addBCC(' hello@>hello.com ', ' assertEquals('=?UTF-8?B?SGVsbG8=?= ',$sm->getBCCStr()); + $this->assertEquals('Hello',$sm->getBCC()['hello@hello.com']); + $this->assertTrue($sm->addBCC('hello@>hello.com ', ' assertEquals('=?UTF-8?B?SGVsbG8y?= ',$sm->getBCCStr()); + $this->assertTrue($sm->addBCC('hello-9@>hello.com ', ' Hel>lo-9',true,true)); + $this->assertEquals('=?UTF-8?B?SGVsbG8y?= ,=?UTF-8?B?SGVsbG8tOQ==?= ',$sm->getBCCStr()); + } + /** + * @test + */ + public function testConstructor00() { + $sm = new EmailMessage('no-reply'); + $this->assertEquals('',$sm->getSMTPServer()->getLastResponse()); + $this->assertSame(0,$sm->getSMTPServer()->getLastResponseCode()); + $this->assertSame(0,$sm->getPriority()); + $this->assertSame(5,$sm->getSMTPServer()->getTimeout()); + } + /** + * @test + */ + public function testSetPriority00() { + $sm = new EmailMessage('no-reply'); + $sm->setPriority(-2); + $this->assertSame(-1,$sm->getPriority()); + $sm->setPriority(100); + $this->assertSame(1,$sm->getPriority()); + $sm->setPriority("hello"); + $this->assertSame(0,$sm->getPriority()); + $sm->setPriority("-26544"); + $this->assertSame(-1,$sm->getPriority()); + $sm->setPriority("26544"); + $this->assertSame(1,$sm->getPriority()); + $sm->setPriority(0); + $this->assertSame(0,$sm->getPriority()); + } + /** + * @test + */ + public function test00() { $this->expectException(\Exception::class); - $this->expectExceptionMessage('The account "smtp-acc-00" has invalid host or port number. Port: 765765, Host: mail.programmingacademia.com.'); - $smtp = new SMTPAccount(); - $smtp->setPassword('izimu#z'); - $smtp->setAddress('test@programmingacademia.com'); - $smtp->setUsername('test@programmingacademia.com'); - $smtp->setServerAddress('mail.programmingacademia.com '); - $smtp->setPort(765765); - $smtp->setAccountName('smtp-acc-00'); - WebFioriApp::getAppConfig()->addAccount($smtp); - $message = new EmailMessage('smtp-acc-00'); - $this->assertTrue($message instanceof EmailMessage); + $this->expectExceptionMessage('No SMTP account was found which has the name "not exist".'); + $message = new EmailMessage('not exist'); } /** * @test */ -// public function test04() { +// public function test01() { +// $smtp = new SMTPAccount(); +// $smtp->setAccountName('smtp-acc-00'); +// //$smtp->setServerAddress('mail.invalid.com'); +// WebFioriApp::getAppConfig()->addAccount($smtp); +// $this->expectException(\Exception::class); +// $this->expectExceptionMessage('The account "smtp-acc-00" has invalid host or port number. Port: 465, Host: .'); +// $message = new EmailMessage('smtp-acc-00'); +// } + /** + * @test + */ +// public function test02() { +// $this->expectException(\Exception::class); +// $this->expectExceptionMessage('The account "smtp-acc-00" has invalid host or port number. Port: 255, Host: mail.programmingacademia.com.'); +// $smtp = new SMTPAccount(); +// $smtp->setPassword('iz1Iimu#z'); +// $smtp->setAddress('test@programmingacademia.com'); +// $smtp->setUsername('test@programmingacademia.com'); +// $smtp->setServerAddress('mail.programmingacademia.com '); +// $smtp->setPort(255); +// $smtp->setAccountName('smtp-acc-00'); +// WebFioriApp::getAppConfig()->addAccount($smtp); +// $message = new EmailMessage('smtp-acc-00'); +// } + /** + * @test + */ +// public function test03() { +// $this->expectException(\Exception::class); +// $this->expectExceptionMessage('The account "smtp-acc-00" has invalid host or port number. Port: 765765, Host: mail.programmingacademia.com.'); // $smtp = new SMTPAccount(); -// $smtp->setPassword('iz2)X1Iimu#z'); +// $smtp->setPassword('izimu#z'); // $smtp->setAddress('test@programmingacademia.com'); // $smtp->setUsername('test@programmingacademia.com'); -// $smtp->setServerAddress('mail.programmingacademia.com '); -// $smtp->setPort(25); -// MailConfig::get()->addSMTPAccount('smtp-acc-00', $smtp); -// $message = EmailMessage::createInstance('smtp-acc-00'); +// $smtp->setServerAddress('mail.programmingacademia.com '); +// $smtp->setPort(765765); +// $smtp->setAccountName('smtp-acc-00'); +// WebFioriApp::getAppConfig()->addAccount($smtp); +// $message = new EmailMessage('smtp-acc-00'); // $this->assertTrue($message instanceof EmailMessage); // } + } From b0da53fa38065a8c8d6e15f94f8da3c867f30111 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Mon, 1 Nov 2021 23:55:47 +0300 Subject: [PATCH 10/16] deprecated class SocketMailer --- tests/entity/mail/SocketMailerTest.php | 88 --- webfiori/framework/mail/SocketMailer.php | 803 ----------------------- 2 files changed, 891 deletions(-) delete mode 100644 tests/entity/mail/SocketMailerTest.php delete mode 100644 webfiori/framework/mail/SocketMailer.php diff --git a/tests/entity/mail/SocketMailerTest.php b/tests/entity/mail/SocketMailerTest.php deleted file mode 100644 index 794bc803e..000000000 --- a/tests/entity/mail/SocketMailerTest.php +++ /dev/null @@ -1,88 +0,0 @@ -assertFalse($sm->addReceiver('', '')); - $this->assertFalse($sm->addReceiver('Hello', '')); - $this->assertFalse($sm->addReceiver('', 'hello@web.com')); - } - /** - * @test - */ - public function testAddReciver01() { - $sm = new SocketMailer(); - $this->assertTrue($sm->addReceiver(' hello.com')); - $this->assertEquals('=?UTF-8?B?SGVsbG8=?= ',$sm->getReceiversStr()); - $this->assertEquals('Hello',$sm->getReceivers()['hello@hello.com']); - $this->assertTrue($sm->addReceiver(' hello.com')); - $this->assertEquals('=?UTF-8?B?SGVsbG8y?= ',$sm->getReceiversStr()); - $this->assertTrue($sm->addReceiver('Hel>lo-9 ', ' hello-9@>hello.com')); - $this->assertEquals('=?UTF-8?B?SGVsbG8y?= ,=?UTF-8?B?SGVsbG8tOQ==?= ',$sm->getReceiversStr()); - } - /** - * @test - */ - public function testAddReciver02() { - $sm = new SocketMailer(); - $this->assertTrue($sm->addReceiver(' hello.com',true)); - $this->assertEquals('=?UTF-8?B?SGVsbG8=?= ',$sm->getCCStr()); - $this->assertEquals('Hello',$sm->getCC()['hello@hello.com']); - $this->assertTrue($sm->addReceiver(' hello.com',true)); - $this->assertEquals('=?UTF-8?B?SGVsbG8y?= ',$sm->getCCStr()); - $this->assertTrue($sm->addReceiver('Hel>lo-9 ', ' hello-9@>hello.com',true)); - $this->assertEquals('=?UTF-8?B?SGVsbG8y?= ,=?UTF-8?B?SGVsbG8tOQ==?= ',$sm->getCCStr()); - } - /** - * @test - */ - public function testAddReciver03() { - $sm = new SocketMailer(); - $this->assertTrue($sm->addReceiver(' hello.com',true,true)); - $this->assertEquals('=?UTF-8?B?SGVsbG8=?= ',$sm->getBCCStr()); - $this->assertEquals('Hello',$sm->getBCC()['hello@hello.com']); - $this->assertTrue($sm->addReceiver(' hello.com',true,true)); - $this->assertEquals('=?UTF-8?B?SGVsbG8y?= ',$sm->getBCCStr()); - $this->assertTrue($sm->addReceiver('Hel>lo-9 ', ' hello-9@>hello.com',true,true)); - $this->assertEquals('=?UTF-8?B?SGVsbG8y?= ,=?UTF-8?B?SGVsbG8tOQ==?= ',$sm->getBCCStr()); - } - /** - * @test - */ - public function testConstructor00() { - $sm = new SocketMailer(); - $this->assertEquals('',$sm->getLastLogMessage()); - $this->assertSame(0,$sm->getLastResponseCode()); - $this->assertSame(0,$sm->getPriority()); - $this->assertSame(5,$sm->getTimeout()); - } - /** - * @test - */ - public function testSetPriority00() { - $sm = new SocketMailer(); - $sm->setPriority(-2); - $this->assertSame(-1,$sm->getPriority()); - $sm->setPriority(100); - $this->assertSame(1,$sm->getPriority()); - $sm->setPriority("hello"); - $this->assertSame(0,$sm->getPriority()); - $sm->setPriority("-26544"); - $this->assertSame(-1,$sm->getPriority()); - $sm->setPriority("26544"); - $this->assertSame(1,$sm->getPriority()); - $sm->setPriority(0); - $this->assertSame(0,$sm->getPriority()); - } -} diff --git a/webfiori/framework/mail/SocketMailer.php b/webfiori/framework/mail/SocketMailer.php deleted file mode 100644 index 75a23f76d..000000000 --- a/webfiori/framework/mail/SocketMailer.php +++ /dev/null @@ -1,803 +0,0 @@ - 'non-urgent', - 0 => 'normal', - 1 => 'urgent' - ]; - /** - * An array that contains an objects of type 'File'. - * @var array - * @since 1.3 - */ - private $attachments; - /** - * An associative array of mail receivers (Blind Carbon Copy). Key represents - * receiver address and the value represents his name. - * @var array - */ - private $bcc; - /** - * A boundary variable used to separate email message parts. - * @var string - * @since 1.3 - */ - private $boundry; - /** - * An associative array of mail receivers (Carbon Copy). Key represents - * receiver address and the value represents his name. - * @var array - */ - private $cc; - /** - * The resource that is used to fire commands - * @var resource - */ - private $conn; - /** - * The name of mail server host. - * @var string - */ - private $host; - /** - * A boolean that is set to true if authentication succeeded. - * @var boolean - * @since 1.2 - */ - private $isLoggedIn; - /** - * The last message that was sent by email server. - * @var string - * @since 1.4 - */ - private $lastResponse; - /** - * Last received code from server after sending some command. - * @var int - */ - private $lastResponseCode; - /** - * The port number. - * @var int - */ - private $port; - /** - * The priority of the message. Affects - * @var int - * @since 1.4.3 - * @see https://tools.ietf.org/html/rfc4021#page-33 - */ - private $priority; - /** - * An associative array of mail receivers. Key represents - * receiver address and value represents his name. - * @var array - */ - private $receivers; - /** - * - * @var array - * @since 1.4.8 - */ - private $responseLog; - /** - * The email address of the sender. - * @var string - */ - private $senderAddress; - /** - * The name of the sender. - * @var string - */ - private $senderName; - /** - * The subject of the email message. - * @var string - */ - private $subject; - /** - * Connection timeout (in minutes) - * @var int - */ - private $timeout; - /** - * A boolean value that is set to true if connection uses SSL. - * @var boolean - * @since 1.4.1 - */ - private $useSsl; - /** - * A boolean value that is set to true if connection uses TLS. - * @var boolean - * @since 1.4.1 - */ - private $useTls; - /** - * If set to true, this means user is in message body writing mode. - * @var boolean - */ - private $writeMode; - /** - * Creates new instance of the class. - * @since 1.0 - */ - public function __construct() { - $this->setTimeout(5); - $this->receivers = []; - $this->cc = []; - $this->bcc = []; - $this->setSubject('Hello From WebFiori Framework'); - $this->writeMode = false; - $this->isLoggedIn = false; - $this->boundry = hash('sha256', date(DATE_ISO8601)); - $this->attachments = []; - $this->lastResponse = ''; - $this->useTls = false; - $this->setPriority(0); - $this->lastResponseCode = 0; - } - /** - * Adds new attachment to the message. - * @param File $attachment An object of type 'File' which contains all - * needed information about the file. It will be added only if the file - * exist in the path or the raw data of the file is set. - * @return boolean If the attachment is added, the method will return true. - * false otherwise. - * @since 1.3 - */ - public function addAttachment($attachment) { - $retVal = false; - - if (class_exists('webfiori\framework\File') && $attachment instanceof File - && (file_exists($attachment->getAbsolutePath()) || file_exists(str_replace('\\', '/', $attachment->getAbsolutePath())) || $attachment->getRawData() !== null)) { - $this->attachments[] = $attachment; - $retVal = true; - } - - return $retVal; - } - /** - * Adds new receiver or updates an existing one. - * @param string $name The name of the email receiver (such as 'Ibrahim'). It - * must be non-empty string. - * @param string $address The email address of the receiver. It must be - * non-empty string. It will act as the identifier for the address. - * @param boolean $isCC If set to true, the receiver will receive - * a carbon copy (CC) of the message. Default is false. - * @param boolean $isBcc If set to true, the receiver will receive - * a blind carbon copy (BCC) of the message. This will override the option $isCC. Default - * is false. - * @since 1.0 - */ - public function addReceiver($name, $address, $isCC = false, $isBcc = false) { - $nameTrimmed = $this->_trimControlChars(str_replace('<', '', str_replace('>', '', $name))); - - if (strlen($nameTrimmed) != 0) { - $addressTrimmed = $this->_trimControlChars(str_replace('<', '', str_replace('>', '', $address))); - - if (strlen($addressTrimmed) != 0) { - if ($isBcc) { - $this->bcc[$addressTrimmed] = $nameTrimmed; - } else if ($isCC) { - $this->cc[$addressTrimmed] = $nameTrimmed; - } else { - $this->receivers[$addressTrimmed] = $nameTrimmed; - } - return true; - } - } - - return false; - } - /** - * Connect to the mail server. - * Before calling this method, the developer must make sure that he set - * connection information correctly (server address and port number). - * @return boolean true if the connection established or already - * connected. false if not. Once the connection is established, the - * method will send the command 'EHLO' to the server. - * @since 1.0 - */ - public function connect() { - $retVal = true; - - if (!$this->isConnected()) { - set_error_handler(function() - { - }); - $portNum = $this->port; - $protocol = ''; - - if ($portNum == 465) { - $protocol = "ssl://"; - } else if ($portNum == 587) { - $protocol = "tls://"; - } - $err = 0; - $errStr = ''; - - if (function_exists('stream_socket_client')) { - $context = stream_context_create([ - 'ssl' => [ - 'verify_peer' => false, - 'verify_peer_name' => false, - 'allow_self_signed' => true - ] - ]); - $this->conn = stream_socket_client($protocol.$this->host.':'.$portNum, $err, $errStr, $this->timeout * 60, STREAM_CLIENT_CONNECT, $context); - } else { - $this->conn = fsockopen($protocol.$this->host, $portNum, $err, $errStr, $this->timeout * 60); - } - set_error_handler(null); - - if (is_resource($this->conn)) { - $this->read(); - - if ($this->sendC('EHLO '.$this->host)) { - $retVal = true; - } - } else { - $retVal = false; - } - } - - return $retVal; - } - /** - * Returns an associative array that contains the names and the addresses - * of people who will receive a blind carbon copy of the message. - * The indices of the array will act as the addresses of the receivers and - * the value of each index will contain the name of the receiver. - * @return array An array that contains receivers information. - * @since 1.4.4 - */ - public function getBCC() { - return $this->bcc; - } - /** - * Returns a string that contains the names and the addresses - * of people who will receive a blind carbon copy of the message. - * The format of the string will be as follows: - *

NAME_1 <ADDRESS_1>, NAME_2 <ADDRESS_2> ...

- * @return string A string that contains receivers information. - * @since 1.0 - */ - public function getBCCStr() { - $arr = []; - - foreach ($this->bcc as $address => $name) { - array_push($arr, '=?UTF-8?B?'.base64_encode($name).'?='.' <'.$address.'>'); - } - - return implode(',', $arr); - } - /** - * Returns an associative array that contains the names and the addresses - * of people who will receive a carbon copy of the message. - * The indices of the array will act as the addresses of the receivers and - * the value of each index will contain the name of the receiver. - * @return array An array that contains receivers information. - * @since 1.4.4 - */ - public function getCC() { - return $this->cc; - } - /** - * Returns a string that contains the names and the addresses - * of people who will receive a carbon copy of the message. - * The format of the string will be as follows: - *

NAME_1 <ADDRESS_1>, NAME_2 <ADDRESS_2> ...

- * @return string A string that contains receivers information. - * @since 1.0 - */ - public function getCCStr() { - $arr = []; - - foreach ($this->cc as $address => $name) { - array_push($arr, '=?UTF-8?B?'.base64_encode($name).'?='.' <'.$address.'>'); - } - - return implode(',', $arr); - } - /** - * Returns the last logged message after executing some command. - * @return string The last logged message after executing some command. Default - * value is empty string. - * @since 1.2 - */ - public function getLastLogMessage() { - return $this->lastResponse; - } - /** - * Returns last response code that was sent by SMTP server after executing - * specific command. - * @return int The last response code that was sent by SMTP server after executing - * specific command. Default return value is 0. - * @since 1.4.7 - */ - public function getLastResponseCode() { - return $this->lastResponseCode; - } - /** - * Returns the priority of the message. - * @return int The priority of the message. -1 for non-urgent, 0 - * for normal and 1 for urgent. Default value is 0. - * @since 1.4.3 - */ - public function getPriority() { - return $this->priority; - } - /** - * Returns an associative array that contains the names and the addresses - * of message receivers. - * The indices of the array will act as the addresses of the receivers and - * the value of each index will contain the name of the receiver. The array - * will only contain the addresses of the people who will receive an original - * copy of the message. - * @return array An array that contains receivers information. - * @since 1.4.4 - */ - public function getReceivers() { - return $this->receivers; - } - /** - * Returns a string that contains the names and the addresses - * of people who will receive an original copy of the message. - * The format of the string will be as follows: - *

NAME_1 <ADDRESS_1>, NAME_2 <ADDRESS_2> ...

- * @return string A string that contains receivers information. - * @since 1.0 - */ - public function getReceiversStr() { - $arr = []; - - foreach ($this->receivers as $address => $name) { - array_push($arr, '=?UTF-8?B?'.base64_encode($name).'?='.' <'.$address.'>'); - } - - return implode(',', $arr); - } - /** - * Returns an array that contains log messages for any SMTP command - * which was sent, - * @return array The array will be indexed. In every index, there - * will be a sub-associative array with the following indices: - *
    - *
  • command
  • - *
  • response-code
  • - *
  • response-message
  • - *
- * @since 1.4.8 - */ - public function getResponsesLog() { - return $this->responseLog; - } - /** - * Returns the email address of the sender. - * @return string The email address of the sender. - * @since 1.1 - */ - public function getSenderAddress() { - return $this->senderAddress; - } - /** - * Returns the name of message sender. - * @return string The name of the sender. - * @since 1.1 - */ - public function getSenderName() { - return $this->senderName; - } - /** - * Returns the time at which the connection will timeout if no response - * was received in minutes. - * @return int Timeout time in minutes. - * @since 1.0 - */ - public function getTimeout() { - return $this->timeout; - } - /** - * Checks if the connection is still open or is it closed. - * @return boolean true if the connection is open. - * @since 1.0 - */ - public function isConnected() { - return is_resource($this->conn); - } - /** - * Checks if the mailer is in message writing mode or not. - * @return boolean true if the mailer is in writing mode. The - * mailer will only switch to writing mode after sending the command 'DATA'. - * @since 1.1 - */ - public function isInWritingMode() { - return $this->writeMode; - } - /** - * Checks if the user is logged in to mail server or not. - * @return boolean The method will return true if the user is - * logged in to the mail server. false if not. - * @since 1.2 - */ - public function isLoggedIn() { - return $this->isLoggedIn; - } - /** - * Sets or gets the value of the property 'useSsl'. - * @param boolean|null $bool true if the connection to the server will use SSL. - * false if not. If null is given, the property will not updated. Default - * is null. - * @return boolean $bool true if the connection to the server will use SSL. - * false if not. Default return value is false - * @since 1.0.1 - * @deprecated since version 1.4.6 - */ - public function isSSL($bool = null) { - if ($bool !== null) { - $this->useSsl = $bool === true; - - if ($this->useSsl) { - $this->useTls = false; - } - } - - return $this->useSsl; - } - /** - * Sets or gets the value of the property 'useTls'. - * @param boolean|null $bool true if the connection to the server will use TLS. - * false if not. If null is given, the property will not updated. Default - * is null. - * @return boolean $bool true if the connection to the server will use TLS. - * false if not. Default return value is false - * @since 1.0.1 - * @deprecated since version 1.4.6 - */ - public function isTLS($bool = null) { - if ($bool !== null) { - $this->useTls = $bool === true; - - if ($this->useTls) { - $this->useSsl = false; - } - } - - return $this->useTls; - } - /** - * Authenticate the user given email server username and password. - * Note that Authentication - * must be done after connecting to the server. - * The user might not be logged - * in in 3 cases: - *
    - *
  • If the mailer is not connected to the email server.
  • - *
  • If the sender address is not set.
  • - *
  • If the given username and password are incorrect.
  • - *
- * @param string $username The email server username. - * @param string $password The user password. - * @return boolean The method will return true if the user is - * logged in to the mail server. false if not. - * @since 1.2 - */ - public function login($username,$password) { - if ($this->isConnected() && strlen($this->getSenderAddress()) != 0) { - $this->sendC('AUTH LOGIN'); - $this->sendC(base64_encode($username)); - $this->sendC(base64_encode($password)); - - if ($this->getLastLogMessage() == '535 Incorrect authentication data') { - return false; - } - //a command to check if authentication is done - $this->sendC('MAIL FROM: <'.$this->getSenderAddress().'>'); - - if ($this->getLastResponseCode() == 235 || - $this->getLastResponseCode() == 250) { - $this->isLoggedIn = true; - } else { - $this->isLoggedIn = false; - } - } - - return $this->isLoggedIn; - } - /** - * Read server response after sending a command to the server. - * @return string - * @since 1.0 - */ - public function read() { - $message = ''; - - while (!feof($this->conn)) { - $str = fgets($this->conn); - $message .= $str; - - if (!isset($str[3]) || (isset($str[3]) && $str[3] == ' ')) { - break; - } - } - $this->_setLastResponseCode($message); - - return $message; - } - /** - * Sends a command to the mail server. - * @param string $command Any SMTP command. - * @return boolean The method will return always true if the command was - * sent. The only case that the method will return false is when it is not - * connected to the server. - * @since 1.0 - */ - public function sendC($command) { - $logEntry = [ - 'command' => $command, - 'response-code' => 0, - 'response-message' => '' - ]; - - if ($this->lastResponseCode >= 400) { - throw new SMTPException('Unable to send SMTP commend "'.$command.'" due to ' - .'error code '.$this->lastResponseCode.' caused by last command. ' - .'Error message: "'.$this->lastResponse.'".'); - } - - if ($this->isConnected()) { - if ($this->isInWritingMode()) { - fwrite($this->conn, $command.self::NL); - - if ($command == self::NL.'.') { - $this->writeMode = false; - } - } else { - fwrite($this->conn, $command.self::NL); - $response = trim($this->read()); - $this->lastResponse = $response; - $logEntry['response-message'] = $response; - $logEntry['response-code'] = $this->getLastResponseCode(); - - if ($command == 'DATA') { - $this->writeMode = true; - } - } - $this->responseLog[] = $logEntry; - - return true; - } else { - $this->responseLog[] = $logEntry; - - return false; - } - } - - /** - * Sets the name of mail server host. - * @param string $host The name of the host (such as mail.mysite.com). - * @since 1.0 - */ - public function setHost($host) { - $this->host = trim($host); - } - /** - * Sets the connection port. - * @param int $port The port number to set. - * @since 1.0 - */ - public function setPort($port) { - if ($port > 0) { - $this->port = $port; - } - } - /** - * Sets the priority of the message. - * @param int $priority The priority of the message. -1 for non-urgent, 0 - * for normal and 1 for urgent. If the passed value is greater than 1, - * then 1 will be used. If the passed value is less than -1, then -1 is - * used. Other than that, 0 will be used. - * @since 1.4.3 - */ - public function setPriority($priority) { - $asInt = intval($priority); - - if ($asInt <= -1) { - $this->priority = -1; - } else if ($asInt >= 1) { - $this->priority = 1; - } else { - $this->priority = 0; - } - } - /** - * Sets the name and the address of the sender. - * @param string $name The name of the sender. - * @param string $address The email address of the sender. - * @since 1.0 - */ - public function setSender($name, $address) { - $this->senderName = $this->_trimControlChars($name); - $this->senderAddress = $this->_trimControlChars($address); - } - /** - * Sets the subject of the message. - * @param string $subject Email subject. - * @since 1.0 - */ - public function setSubject($subject) { - $trimmed = $this->_trimControlChars($subject); - - if (strlen($trimmed) > 0) { - $this->subject = $trimmed; - } - } - /** - * Sets the timeout time of the connection. - * @param int $val The value of timeout (in minutes). The timeout will be updated - * only if the connection is not yet established and the given value is grater - * than 0. - */ - public function setTimeout($val) { - if ($val >= 1 && !$this->isConnected()) { - $this->timeout = $val; - } - } - /** - * Write a message to the buffer. - * Note that this method will trim the following character from the string - * if they are found in the message: '\t\n\r\0\x0B\0x1B\0x0C'. - * @param string $msg The message to write. - * @param boolean $sendMessage If set to true, The connection will be closed and the - * message will be sent. - * @since 1.0 - */ - public function write($msg,$sendMessage = false) { - if ($this->isInWritingMode()) { - $this->sendC($this->_trimControlChars($msg)); - - if ($sendMessage === true) { - $this->_appendAttachments(); - $this->sendC(self::NL.'.'); - $this->sendC('QUIT'); - } - } else if (strlen($this->getSenderAddress()) != 0) { - $this->_receiversCommand(); - $this->sendC('DATA'); - $importanceHeaderVal = $this->_priorityCommand(); - - $this->sendC('Content-Transfer-Encoding: quoted-printable'); - $this->sendC('Importance: '.$importanceHeaderVal); - $this->sendC('From: =?UTF-8?B?'. base64_encode($this->getSenderName()).'?= <'.$this->getSenderAddress().'>'); - $this->sendC('To: '.$this->getReceiversStr()); - $this->sendC('CC: '.$this->getCCStr()); - $this->sendC('BCC: '.$this->getBCCStr()); - $this->sendC('Date:'.date('r (T)')); - $this->sendC('Subject:'.'=?UTF-8?B?'.base64_encode($this->subject).'?='); - $this->sendC('MIME-Version: 1.0'); - $this->sendC('Content-Type: multipart/mixed; boundary="'.$this->boundry.'"'.self::NL); - $this->sendC('--'.$this->boundry); - $this->sendC('Content-Type: text/html; charset="UTF-8"'.self::NL); - $this->sendC($this->_trimControlChars($msg)); - - if ($sendMessage === true) { - $this->_appendAttachments(); - $this->sendC(self::NL.'.'); - $this->sendC('QUIT'); - } - } - } - /** - * A method that is used to include email attachments. - * @since 1.3 - */ - private function _appendAttachments() { - if (count($this->attachments) != 0) { - foreach ($this->attachments as $file) { - if ($file->getRawData() === null) { - $file->read(); - } - $content = $file->getRawData(); - $contentChunk = chunk_split(base64_encode($content)); - $this->sendC('--'.$this->boundry); - $this->sendC('Content-Type: '.$file->getFileMIMEType().'; name="'.$file->getName().'"'); - $this->sendC('Content-Transfer-Encoding: base64'); - $this->sendC('Content-Disposition: attachment; filename="'.$file->getName().'"'.self::NL); - $this->sendC($contentChunk); - } - $this->sendC('--'.$this->boundry.'--'); - } - } - private function _priorityCommand() { - $priorityAsInt = $this->getPriority(); - $priorityHeaderVal = self::PRIORITIES[$priorityAsInt]; - - if ($priorityAsInt == -1) { - $importanceHeaderVal = 'low'; - } else if ($priorityAsInt == 1) { - $importanceHeaderVal = 'High'; - } else { - $importanceHeaderVal = 'normal'; - } - $this->sendC('Priority: '.$priorityHeaderVal); - - return $importanceHeaderVal; - } - private function _receiversCommand() { - foreach ($this->receivers as $address => $name) { - $this->sendC('RCPT TO: <'.$address.'>'); - } - - foreach ($this->cc as $address => $name) { - $this->sendC('RCPT TO: <'.$address.'>'); - } - - foreach ($this->bcc as $address => $name) { - $this->sendC('RCPT TO: <'.$address.'>'); - } - } - /** - * Sets the code that was the result of executing SMTP command. - * @param string $serverResponseMessage The last message which was sent by - * the server after executing specific command. - * @since 1.4.7 - */ - private function _setLastResponseCode($serverResponseMessage) { - $firstNum = $serverResponseMessage[0]; - $firstAsInt = intval($firstNum); - - if ($firstAsInt != 0) { - $secNum = $serverResponseMessage[1]; - $thirdNum = $serverResponseMessage[2]; - $this->lastResponseCode = intval($firstNum) * 100 + (intval($secNum * 10)) + (intval($thirdNum)); - } - } - /** - * Removes control characters from the start and end of string in addition - * to white spaces. - * @param string $str The string that will be trimmed - * @return string The string after its control characters trimmed. - */ - private function _trimControlChars($str) { - return trim($str, "\x00..\x20"); - } -} From 9d4d9bfab12bd8d2ecd5bf2658c8dee971b8bfc2 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Mon, 1 Nov 2021 23:56:06 +0300 Subject: [PATCH 11/16] Update AddCommand.php --- webfiori/framework/cli/commands/AddCommand.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/webfiori/framework/cli/commands/AddCommand.php b/webfiori/framework/cli/commands/AddCommand.php index 4dc237b87..84b5c545d 100644 --- a/webfiori/framework/cli/commands/AddCommand.php +++ b/webfiori/framework/cli/commands/AddCommand.php @@ -31,6 +31,7 @@ use webfiori\framework\DB; use webfiori\framework\mail\SMTPAccount; use webfiori\framework\WebFioriApp; +use webfiori\framework\mail\SMTPServer; /** * A command which is used to add a database connection or SMTP account. @@ -142,9 +143,10 @@ private function _addSmtp() { $smtpConn->setSenderName($this->getInput('Sender name:', 'WebFiori Framework')); $smtpConn->setAccountName($this->getInput('Give your connection a friendly name:', 'smtp-connection-'.count(WebFioriApp::getAppConfig()->getAccounts()))); $this->println('Testing connection. This can take up to 1 minute...'); - $result = ConfigController::get()->getSocketMailer($smtpConn); + $server = new SMTPServer($smtpConn->getServerAddress(), $smtpConn->getPort()); + - if (gettype($result) == 'object') { + if ($server->authLogin($smtpConn->getUsername(), $smtpConn->getPassword())) { $this->success('Connectd. Adding connection information...'); ConfigController::get()->updateOrAddEmailAccount($smtpConn); $this->success('Connection information was stored in the class "'.APP_DIR_NAME.'\\AppConfig".'); @@ -152,7 +154,7 @@ private function _addSmtp() { return 0; } else { $this->error('Unable to connect to SMTP server.'); - $this->println('Error Information: '.$result); + $this->println('Error Information: '.$server->getLastResponse()); return -1; } From 75439e3e03e278d191656b044ff43dd9c4de0b51 Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Mon, 1 Nov 2021 23:56:13 +0300 Subject: [PATCH 12/16] Update ConfigController.php --- webfiori/framework/ConfigController.php | 72 +++---------------------- 1 file changed, 6 insertions(+), 66 deletions(-) diff --git a/webfiori/framework/ConfigController.php b/webfiori/framework/ConfigController.php index aab5c3bb7..cfec6b0f2 100644 --- a/webfiori/framework/ConfigController.php +++ b/webfiori/framework/ConfigController.php @@ -29,6 +29,7 @@ use webfiori\framework\exceptions\SMTPException; use webfiori\framework\mail\SMTPAccount; use webfiori\framework\mail\SocketMailer; +use webfiori\framework\mail\SMTPServer; /** * A class that can be used to modify basic configuration settings of @@ -632,49 +633,6 @@ public function getSMTPAccounts() { return []; } - /** - * Returns a new instance of the class SocketMailer. - * - * The method will try to establish a connection to SMTP server using - * the given SMTP account. - * - * @param SMTPAccount $emailAcc An account that is used to initiate - * socket mailer. - * - * @return SocketMailer|string The method will return an instance of SocketMailer - * on successful connection. If no connection is established, the method will - * return MailFunctions::INV_HOST_OR_PORT. If user authentication fails, - * the method will return 'MailFunctions::INV_CREDENTIALS'. - * - * @since 1.0 - */ - public function getSocketMailer(SMTPAccount $emailAcc) { - if ($emailAcc instanceof SMTPAccount) { - $retVal = self::INV_HOST_OR_PORT; - $m = new SocketMailer(); - - $m->setHost($emailAcc->getServerAddress()); - $m->setPort($emailAcc->getPort()); - - if ($m->connect()) { - try { - $m->setSender($emailAcc->getSenderName(), $emailAcc->getAddress()); - - if ($m->login($emailAcc->getUsername(), $emailAcc->getPassword())) { - $retVal = $m; - } else { - $retVal = self::INV_CREDENTIALS; - } - } catch (\Exception $ex) { - throw new SMTPException($ex->getMessage()); - } - } - - return $retVal; - } - - return false; - } /** * Returns an array that holds different page titles for the web application * on different languages. @@ -826,33 +784,15 @@ public function updateCronPassword($newPass) { * * @param SMTPAccount $emailAccount An instance of 'SMTPAccount'. * - * @return boolean|string The method will return true if the email - * account was updated or added. If the email account contains wrong server - * information, the method will return MailFunctions::INV_HOST_OR_PORT. - * If the given email account contains wrong login info, the method will - * return MailFunctions::INV_CREDENTIALS. Other than that, the method - * will return false. * * @since 1.1 */ public function updateOrAddEmailAccount(SMTPAccount $emailAccount) { - $retVal = false; - - if ($emailAccount instanceof SMTPAccount) { - $sm = $this->getSocketMailer($emailAccount); - - if ($sm instanceof SocketMailer) { - $accountsArr = $this->getSMTPAccounts(); - $accountsArr[$emailAccount->getAccountName()] = $emailAccount; - $this->writeAppConfig([ - 'smtp' => $accountsArr - ]); - $retVal = true; - } - $retVal = $sm; - } - - return $retVal; + $accountsArr = $this->getSMTPAccounts(); + $accountsArr[$emailAccount->getAccountName()] = $emailAccount; + $this->writeAppConfig([ + 'smtp' => $accountsArr + ]); } /** * Updates web site configuration based on some attributes. From 4598ab0a90745603082e3522c27d3a0eccca075f Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Mon, 1 Nov 2021 23:56:20 +0300 Subject: [PATCH 13/16] Update EmailMessage.php --- webfiori/framework/mail/EmailMessage.php | 45 +++++++++++++----------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/webfiori/framework/mail/EmailMessage.php b/webfiori/framework/mail/EmailMessage.php index 6b85e6167..b2a7aadd8 100644 --- a/webfiori/framework/mail/EmailMessage.php +++ b/webfiori/framework/mail/EmailMessage.php @@ -163,6 +163,7 @@ public function setPriority($priority) { */ public function __construct($sendAccountName = '') { $this->log = []; + $this->subject = 'Hello From WebFiori Framework'; $this->setPriority(0); $this->boundry = hash('sha256', date(DATE_ISO8601)); $this->receiversArr = [ @@ -179,6 +180,7 @@ public function __construct($sendAccountName = '') { if ($acc instanceof SMTPAccount) { $this->smtpAcc = $acc; + $this->smtpServer = new SMTPServer($acc->getServerAddress(), $acc->getPort()); return; } throw new SMTPException('No SMTP account was found which has the name "'.$sendAccountName.'".'); @@ -296,7 +298,7 @@ private function _getReceiversStr($type) { $arr = []; foreach ($this->receiversArr[$type] as $address => $name) { - array_push($arr, '=?UTF-8?B?'.base64_encode($name).'?='.' <'.$address.'>'); + $arr[] = '=?UTF-8?B?'.base64_encode($name).'?='.' <'.$address.'>'; } return implode(',', $arr); @@ -313,7 +315,7 @@ private function _getReceiversStr($type) { * @since 1.0.3 */ public function getBCCStr() { - $this->_getReceiversStr('bcc'); + return $this->_getReceiversStr('bcc'); } /** * Returns an associative array that contains the names and the addresses @@ -327,7 +329,7 @@ public function getBCCStr() { * @since 1.0.2 */ public function getCC() { - $this->receiversArr['cc']; + return $this->receiversArr['cc']; } /** * Returns a string that contains the names and the addresses @@ -341,7 +343,7 @@ public function getCC() { * @since 1.0.3 */ public function getCCStr() { - $this->_getReceiversStr('cc'); + return $this->_getReceiversStr('cc'); } /** * Returns a child node given its ID. @@ -371,11 +373,7 @@ public function getChildByID($id) { * @since 1.0.4 */ public function getLog() { - if ($this->_getSocketMailer() !== null) { - return $this->_getSocketMailer()->getResponsesLog(); - } - - return $this->log; + return $this->smtpServer->getLog(); } /** * Returns an associative array that contains the names and the addresses @@ -452,19 +450,19 @@ public function send() { $this->smtpServer->sendCommand('Content-Transfer-Encoding: quoted-printable'); $this->smtpServer->sendCommand('Importance: '.$importanceHeaderVal); - $this->smtpServer->sendCommand('From: =?UTF-8?B?'. base64_encode($acc->getSenderName()).'?= <'.$acc->getSenderName().'>'); + $this->smtpServer->sendCommand('From: =?UTF-8?B?'. base64_encode($acc->getSenderName()).'?= <'.$acc->getAddress().'>'); $this->smtpServer->sendCommand('To: '.$this->getToStr()); $this->smtpServer->sendCommand('CC: '.$this->getCCStr()); $this->smtpServer->sendCommand('BCC: '.$this->getBCCStr()); $this->smtpServer->sendCommand('Date:'.date('r (T)')); $this->smtpServer->sendCommand('Subject:'.'=?UTF-8?B?'.base64_encode($this->getSubject()).'?='); $this->smtpServer->sendCommand('MIME-Version: 1.0'); - $this->smtpServer->sendCommand('Content-Type: multipart/mixed; boundary="'.$this->boundry.'"'.self::NL); + $this->smtpServer->sendCommand('Content-Type: multipart/mixed; boundary="'.$this->boundry.'"'.SMTPServer::NL); $this->smtpServer->sendCommand('--'.$this->boundry); - $this->smtpServer->sendCommand('Content-Type: text/html; charset="UTF-8"'.self::NL); + $this->smtpServer->sendCommand('Content-Type: text/html; charset="UTF-8"'.SMTPServer::NL); $this->smtpServer->sendCommand($this->_trimControlChars($this->getDocument()->toHTML())); $this->_appendAttachments(); - $this->smtpServer->sendCommand(self::NL.'.'); + $this->smtpServer->sendCommand(SMTPServer::NL.'.'); $this->smtpServer->sendCommand('QUIT'); } else { @@ -484,13 +482,13 @@ private function _appendAttachments() { } $content = $file->getRawData(); $contentChunk = chunk_split(base64_encode($content)); - $this->smtpServer->sendCommand('--'.$this->boundry); + $this->smtpServer->sendCommand('--'.$this->boundry.SMTPServer::NL); $this->smtpServer->sendCommand('Content-Type: '.$file->getFileMIMEType().'; name="'.$file->getName().'"'); $this->smtpServer->sendCommand('Content-Transfer-Encoding: base64'); - $this->smtpServer->sendCommand('Content-Disposition: attachment; filename="'.$file->getName().'"'.self::NL); + $this->smtpServer->sendCommand('Content-Disposition: attachment; filename="'.$file->getName().'"'.SMTPServer::NL); $this->smtpServer->sendCommand($contentChunk); } - $this->smtpServer->sendCommand('--'.$this->boundry.'--'); + $this->smtpServer->sendCommand('--'.$this->boundry.'--'.SMTPServer::NL); } } /** @@ -532,14 +530,19 @@ public function getSMTPAccount() { return $this->smtpAcc; } /** - * Adds a text node to the body of the message. + * Returns an object that holds SMTP server information. * - * @param string $text The text that will be in the body of the node. + * The returned instance can be used to access SMTP server messages log + * to see if the message was transfered or not. Note that the + * connection to the server will only be established once the + * method 'EmailMessage::send()'. * - * @since 1.0 + * @return SMTPServer An instance which represents SMTP server. + * + * @since 1.0.5 */ - public function write($text) { - $this->getDocument()->addChild(HTMLNode::createTextNode($text,false)); + public function getSMTPServer() { + return $this->smtpServer; } /** * Returns the document that is associated with the page. From a96984253dbf09b0745071a9bdd22993b9844aea Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Mon, 1 Nov 2021 23:56:28 +0300 Subject: [PATCH 14/16] Update SMTPServer.php --- webfiori/framework/mail/SMTPServer.php | 36 +++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 3 deletions(-) diff --git a/webfiori/framework/mail/SMTPServer.php b/webfiori/framework/mail/SMTPServer.php index 789300530..135b3ddaa 100644 --- a/webfiori/framework/mail/SMTPServer.php +++ b/webfiori/framework/mail/SMTPServer.php @@ -10,6 +10,7 @@ * @version 1.0 */ class SMTPServer { + private $isWriting; private $lastCommand; const NL = "\r\n"; private $serverOptions; @@ -76,6 +77,9 @@ public function __construct($serverAddress, $port) { $this->host = $serverAddress; $this->serverOptions = []; $this->timeout = 5; + $this->lastResponse = ''; + $this->lastResponseCode = 0; + $this->isWriting = false; } /** * Use plain authorization method to log in the user to SMTP server. @@ -338,9 +342,22 @@ public function sendCommand($command) { if ($this->isConnected()) { fwrite($this->conn, $command.self::NL); - $response = trim($this->read()); - $this->lastResponse = $response; - $this->_log($command, $this->getLastResponseCode(), $response); + if (!$this->isInWritingMode()) { + $response = trim($this->read()); + $this->lastResponse = $response; + $this->_log($command, $this->getLastResponseCode(), $response); + } else { + $this->_log($command, 0, '-'); + } + if ($command == 'DATA') { + $this->isWriting = true; + } + if ($command == self::NL.'.') { + $this->writeMode = false; + $response = trim($this->read()); + $this->lastResponse = $response; + $this->_log($command, $this->getLastResponseCode(), $response); + } return true; } else { @@ -349,6 +366,19 @@ public function sendCommand($command) { return false; } } + /** + * Checks if the server is in message writing mode. + * + * The server will be in writing mode if the command 'DATA' was sent. + * + * @return boolean If the server is in message writing mode, the method + * will return true. False otherwise. + * + * @since 1.0 + */ + public function isInWritingMode() { + return $this->isWriting; + } private function _switchToTls() { $this->sendCommand('STARTTLS'); $cryptoMethod = STREAM_CRYPTO_METHOD_TLS_CLIENT; From 0b8a00c3ed2f68dc3ed5bc20f1d35e5ec1c54f3f Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 2 Nov 2021 21:25:39 +0300 Subject: [PATCH 15/16] Update CreateTableObj.php --- webfiori/framework/cli/helpers/CreateTableObj.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webfiori/framework/cli/helpers/CreateTableObj.php b/webfiori/framework/cli/helpers/CreateTableObj.php index df4a9a952..6e5c4ac59 100644 --- a/webfiori/framework/cli/helpers/CreateTableObj.php +++ b/webfiori/framework/cli/helpers/CreateTableObj.php @@ -285,7 +285,7 @@ private function _setSize($colObj) { $colDataType = $colObj->getDatatype(); $dataSize = $this->_getCommand()->getInput('Enter column size:'); - if ($colObj->getDatatype() == 'varchar' && $dataSize > 21845) { + if ($colObj instanceof MySQLColumn && $colObj->getDatatype() == 'varchar' && $dataSize > 21845) { $this->_getCommand()->warning('The data type "varchar" has a maximum size of 21845. The ' .'data type of the column will be changed to "mediumtext" if you continue.'); From e5c2d2c7e16983251dd24d35dc0d5419d4e3415c Mon Sep 17 00:00:00 2001 From: Ibrahim BinAlshikh Date: Tue, 2 Nov 2021 21:40:30 +0300 Subject: [PATCH 16/16] Update SMTPServer.php --- webfiori/framework/mail/SMTPServer.php | 101 ++++++++++++------------- 1 file changed, 49 insertions(+), 52 deletions(-) diff --git a/webfiori/framework/mail/SMTPServer.php b/webfiori/framework/mail/SMTPServer.php index 135b3ddaa..0c8092d67 100644 --- a/webfiori/framework/mail/SMTPServer.php +++ b/webfiori/framework/mail/SMTPServer.php @@ -19,7 +19,7 @@ class SMTPServer { * * @var resource */ - private $conn; + private $serverCon; /** * The name of mail server host. * @@ -27,7 +27,7 @@ class SMTPServer { * * @since 1.0 */ - private $host; + private $serverHost; /** * The last message that was sent by email server. * @@ -51,7 +51,7 @@ class SMTPServer { * * @since 1.0 */ - private $port; + private $serverPort; /** * * @var array @@ -64,7 +64,7 @@ class SMTPServer { * * @var int */ - private $timeout; + private $responseTimeout; /** * Initiates new instance of the class. * @@ -73,10 +73,10 @@ class SMTPServer { * @param string $port SMTP server port such as 25, 465 or 587. */ public function __construct($serverAddress, $port) { - $this->port = $port; - $this->host = $serverAddress; + $this->serverPort = $port; + $this->serverHost = $serverAddress; $this->serverOptions = []; - $this->timeout = 5; + $this->responseTimeout = 5; $this->lastResponse = ''; $this->lastResponseCode = 0; $this->isWriting = false; @@ -197,13 +197,22 @@ private function _tryConnect($protocol) { $conn = stream_socket_client($protocol.$host.':'.$portNum, $err, $errStr, $timeout * 60, STREAM_CLIENT_CONNECT, $context); } else { $this->_log('Connect', 0, 'Trying to connect to the server using "fsockopen"...'); - $conn = fsockopen($protocol.$this->host, $portNum, $err, $errStr, $timeout * 60); + $conn = fsockopen($protocol.$this->serverHost, $portNum, $err, $errStr, $timeout * 60); } if (!is_resource($conn)) { $this->_log('Connect', $err, 'Faild to connect: '.$errStr); } return $conn; } + private function _getTransport() { + $port = $this->getPort(); + if ($port == 465) { + return "ssl://"; + } else if ($port == 587) { + return "tls://"; + } + return ''; + } /** * Connects to SMTP server. * @@ -217,49 +226,45 @@ public function connect() { $retVal = true; if (!$this->isConnected()) { - set_error_handler(function($errno, $errstr, $errfile, $errline) - { - }); - $portNum = $this->getPort(); - $protocol = ''; - - if ($portNum == 465) { - $protocol = "ssl://"; - } else if ($portNum == 587) { - $protocol = "tls://"; - } + set_error_handler(null); + $transport = $this->_getTransport(); $err = 0; $errStr = ''; - $this->conn = $this->_tryConnect($protocol, $err, $errStr); + $this->serverCon = $this->_tryConnect($transport, $err, $errStr); - if ($this->conn === false) { - $this->conn = $this->_tryConnect('', $err, $errStr); + if ($this->serverCon === false) { + $this->serverCon = $this->_tryConnect('', $err, $errStr); } - - set_error_handler(null); - if (is_resource($this->conn)) { + if (is_resource($this->serverCon)) { $this->_log('-', 0, $this->read()); if ($this->sendHello()) { - if (in_array('STARTTLS', $this->getServerOptions())) { - if ($this->_switchToTls()) { - $this->sendHello(); - $retVal = true; - } else { - $retVal = false; - } - } else { - $retVal = true; - } + //We might need to switch to secure connection. + $retVal = $this->_checkStartTls(); + } else { + $retVal = false; } } else { $retVal = false; } + restore_error_handler(); } return $retVal; } + private function _checkStartTls() { + if (in_array('STARTTLS', $this->getServerOptions())) { + if ($this->_switchToTls()) { + $this->sendHello(); + return true; + } else { + return false; + } + } else { + return true; + } + } /** * Read server response after sending a command to the server. * @@ -270,8 +275,8 @@ public function connect() { public function read() { $message = ''; - while (!feof($this->conn)) { - $str = fgets($this->conn); + while (!feof($this->serverCon)) { + $str = fgets($this->serverCon); $message .= $str; @@ -341,7 +346,7 @@ public function sendCommand($command) { } if ($this->isConnected()) { - fwrite($this->conn, $command.self::NL); + fwrite($this->serverCon, $command.self::NL); if (!$this->isInWritingMode()) { $response = trim($this->read()); $this->lastResponse = $response; @@ -389,19 +394,11 @@ private function _switchToTls() { } - set_error_handler(function ($errno, $errstr, $errfile, $errline) - { - echo 'ErrorNo: '.$errno."\n"; - echo 'ErrorLine: '.$errline."\n"; - echo 'ErrorStr: '.$errstr."\n"; - - }); $success = stream_socket_enable_crypto( - $this->conn, + $this->serverCon, true, $cryptoMethod ); - restore_error_handler(); return $success === true; } @@ -434,7 +431,7 @@ public function sendHello() { */ public function setTimeout($val) { if ($val >= 1 && !$this->isConnected()) { - $this->timeout = $val; + $this->responseTimeout = $val; } } /** @@ -445,7 +442,7 @@ public function setTimeout($val) { * @since 1.0 */ public function getPort() { - return $this->port; + return $this->serverPort; } /** * Returns SMTP server host address. @@ -455,7 +452,7 @@ public function getPort() { * @since 1.0 */ public function getHost() { - return $this->host; + return $this->serverHost; } /** * Returns the time at which the connection will timeout if no response @@ -466,7 +463,7 @@ public function getHost() { * @since 1.0 */ public function getTimeout() { - return $this->timeout; + return $this->responseTimeout; } /** * Checks if the connection is still open or is it closed. @@ -476,6 +473,6 @@ public function getTimeout() { * @since 1.0 */ public function isConnected() { - return is_resource($this->conn); + return is_resource($this->serverCon); } }