From b393e9d5b5fba93468c64e9d25d4e285dab0a3fb Mon Sep 17 00:00:00 2001
From: Naufal Azkia <66082449+naufkia@users.noreply.github.com>
Date: Thu, 29 Sep 2022 11:05:33 +0700
Subject: [PATCH] =?UTF-8?q?=F0=9F=92=A6=20add:=20Mailer=20Classes?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
core/Conf/Kyaaaa/Mailer.php | 441 ++++++++++++++++++++++++++++++++++++
1 file changed, 441 insertions(+)
create mode 100644 core/Conf/Kyaaaa/Mailer.php
diff --git a/core/Conf/Kyaaaa/Mailer.php b/core/Conf/Kyaaaa/Mailer.php
new file mode 100644
index 0000000..3a2865c
--- /dev/null
+++ b/core/Conf/Kyaaaa/Mailer.php
@@ -0,0 +1,441 @@
+host = $host;
+ $this->port = (int) $port;
+ $this->secure = strtolower($secure);
+ $this->username = $username;
+ $this->password = $password;
+
+ if (PHP_SAPI === 'cli') {
+ $this->debug = true;
+ }
+ }
+
+ public function __destruct()
+ {
+ $this->close();
+ }
+
+ public function setFrom($address, $name = '')
+ {
+ if ($address = $this->valid($address)) {
+ $this->from = [$address, $this->stripnl($name)];
+ }
+ }
+
+ public function addReplyTo($address, $name = '')
+ {
+ if ($address = $this->valid($address)) {
+ if(!isset($this->replyTo[$address])) {
+ $this->replyTo[$address] = [$address, $this->stripnl($name)];
+ }
+ }
+ }
+
+ public function setTo($address, $name = '')
+ {
+ $this->addTo($address, $name);
+ }
+
+ public function addTo($address, $name = '')
+ {
+ if ($address = $this->valid($address)) {
+ if(!isset($this->recipients[$address])) {
+ $this->to[] = [$address, $this->stripnl($name)];
+ $this->recipients[$address] = true;
+ }
+ }
+ }
+
+ public function addCc($address, $name = '')
+ {
+ if ($address = $this->valid($address)) {
+ if(!isset($this->recipients[$address])) {
+ $this->cc[] = [$address, $this->stripnl($name)];
+ $this->recipients[$address] = true;
+ }
+ }
+ }
+
+ public function addBcc($address, $name = '')
+ {
+ if ($address = $this->valid($address)) {
+ if(!isset($this->recipients[$address])) {
+ $this->bcc[] = [$address, $this->stripnl($name)];
+ $this->recipients[$address] = true;
+ }
+ }
+ }
+
+ public function addFile($file)
+ {
+ if ($file && is_file($file)) {
+ $this->files[] = $file;
+ }
+ }
+
+ private function init()
+ {
+ if (strpos($this->host, ':')) {
+ $this->host = preg_replace(['#[\w]+://#', '#:[\w]+#'], '', $this->host);
+ }
+
+ if (empty($this->to)) {
+ throw new \Exception('ERROR: A valid email address to send to is required');
+ }
+
+ if (strlen(trim($this->body)) < 3) {
+ throw new \Exception('ERROR: Message body empty');
+ }
+
+ if ($this->secure === 'ssl') {
+ $this->host = $this->secure . '://' . $this->host;
+ }
+
+ if (empty($this->from)) {
+ $this->from = [$this->username, ''];
+ }
+
+ if (empty($this->hostname)) {
+ $this->hostname = gethostname();
+ }
+ }
+
+ public function send()
+ {
+ try {
+ $this->init();
+ $this->connect();
+ $this->auth();
+
+ $this->request('MAIL FROM: <'.$this->from[0].'>', 250);
+ $addresses = array_keys($this->recipients);
+ foreach($addresses as $address) {
+ $this->request('RCPT TO: <'. $address . '>', 250);
+ }
+
+ $this->request('DATA', 354);
+ $this->request($this->createMessage(), 250);
+ $this->request('QUIT', 221);
+
+ return true;
+ } catch(\Exception $e) {
+ $this->debug($e->getMessage());
+ return false;
+ }
+
+ return true;
+ }
+
+ public function setSubject($subject = '')
+ {
+ $this->subject = $subject;
+ }
+
+ public function setBody($body = '')
+ {
+ $this->body = $body;
+ }
+
+ public function createMessage()
+ {
+ $message = [];
+ $message[] = $this->createHeader();
+ $message[] = $this->createContent();
+ return implode(static::NL, $message);
+ }
+
+ private function createHeader()
+ {
+ $headers[] = 'Date: '. date('r');
+ $headers[] = 'To: '.$this->concatAddress($this->to);
+ $headers[] = 'From: '.$this->formatAddress($this->from);
+
+ if (!empty($this->cc)) {
+ $headers[] = 'Cc: '.$this->concatAddress($this->cc);
+ }
+
+ if (!empty($this->bcc)) {
+ $headers[] = 'Bcc: '.$this->concatAddress($this->bcc);
+ }
+
+ if (!empty($this->replyTo)) {
+ $headers[] = 'Reply-To: '.$this->concatAddress($this->replyTo);
+ }
+
+ $headers[] = iconv_mime_encode('Subject', $this->subject);
+ $headers[] = 'Message-ID: '. $this->generateMessageId();
+ $headers[] = 'X-Mailer: SMTPMailer v1.0.0 https://github.com/smtpmailer/smtpmailer';
+ $headers[] = 'MIME-Version: 1.0';
+
+ return implode(static::NL, $headers);
+ }
+
+ private function createContent()
+ {
+ $boundary = md5(uniqid());
+
+ $contents = [];
+
+ if (empty($this->text)) {
+ $this->text = $this->html2text($this->body);
+ }
+
+ $contents[] = 'Content-Type: multipart/'. (!empty($this->files) ? 'mixed' : 'alternative') .'; boundary="'.$boundary.'"';
+ $contents[] = '';
+ $contents[] = 'This is a multi-part message in MIME format.';
+ $contents[] = '--'. $boundary;
+
+ $contents[] = $this->formatContent('plain', 'text');
+ $contents[] = '--'. $boundary;
+
+ $contents[] = $this->formatContent('html', 'body');
+ $contents[] = '--'. $boundary;
+
+ if (!empty($this->files)) {
+ $contents[] = $this->createAttachment($boundary);
+ }
+
+ $contents[count($contents)-1] .= '--';
+ $contents[] = ".";
+
+ return implode(static::NL, $contents);
+ }
+
+ private function formatContent($type, $content)
+ {
+ $contents[] = 'Content-Type: text/'.$type.'; charset="'.$this->charset.'"';
+ $contents[] = 'Content-Transfer-Encoding: '. $this->encoding;
+ $contents[] = '';
+ $contents[] = ($this->encoding == 'quoted-printable') ? quoted_printable_encode($this->$content) : $this->$content;
+ return implode(static::NL, $contents);
+ }
+
+ private function createAttachment($boundary)
+ {
+ $contents = [];
+ foreach ($this->files as $file) {
+ if (file_exists($file)) {
+ $contents[] = 'Content-Type: application/octet-stream; '.'name="'. basename($file) .'"';
+ $contents[] = 'Content-Transfer-Encoding: base64';
+ $contents[] = 'Content-Disposition: attachment';
+ $contents[] = '';
+ $contents[] = chunk_split(base64_encode(file_get_contents($file)));
+ $contents[] = '--'.$boundary;
+ }
+ }
+ return implode(static::NL, $contents);
+ }
+
+ public function connect()
+ {
+ $this->debug("Connecting to {$this->host}:{$this->port}");
+
+ $this->connection = @stream_socket_client($this->host . ':' . $this->port, $errno, $errstr, $this->timeout);
+
+ if (!$this->connection) {
+ extract(error_get_last());
+ throw new \Exception("ERROR: $errstr in $file on line $line");
+ }
+
+ $this->debug('Connecting successfully');
+
+ $this->response(220);
+
+ $this->request('EHLO '.$this->hostname, 250);
+
+ if ($this->secure === 'tls' || isset($this->extensions['STARTTLS'])) {
+ $this->request('STARTTLS', 220);
+ stream_socket_enable_crypto($this->connection, true, STREAM_CRYPTO_METHOD_TLS_CLIENT);
+ }
+
+ return true;
+ }
+
+ private function auth()
+ {
+ if (!empty($this->extensions['AUTH'])) {
+
+ if (empty($this->username) || empty($this->password)) {
+ throw new \Exception('ERROR: SMTP username/password for '. $this->host . ' is required');
+ }
+
+ $authType = $this->extensions['AUTH'];
+
+ switch($authType) {
+ case 'PLAIN':
+ $this->request('AUTH PLAIN', 334);
+ $this->request(base64_encode("\0" . $this->username . "\0" . $this->password), 235);
+ break;
+ case 'LOGIN':
+ $this->request('AUTH LOGIN', 334);
+ $this->request(base64_encode($this->username), 334);
+ $this->request(base64_encode($this->password), 235);
+ break;
+ default:
+ throw new \Exception('ERROR: Authentication method "'. $authType . '" is not supported');
+ }
+ }
+ return true;
+ }
+
+ private function request($cmd, $code)
+ {
+ $this->debug('REQUEST: ' . $cmd);
+ fwrite($this->connection, $cmd . static::NL);
+
+ $prefix = substr($cmd, 0, 4);
+ return $this->response($code, $prefix);
+ }
+
+ private function response($code, $prefix = '')
+ {
+ stream_set_timeout($this->connection, $this->timeout);
+ $result = fread($this->connection, 768);
+
+ $meta = stream_get_meta_data($this->connection);
+ if ($meta['timed_out']) {
+ throw new \Exception('ERROR: Stream socket timed-out (' . $this->timeout . 's)');
+ }
+
+ if (substr($result, 0, 3) != $code) {
+ throw new \Exception($result);
+ }
+
+ $this->debug("RESPONSE: " . $result);
+
+ if ($prefix === 'EHLO') {
+ $this->parseEhloResponse($result);
+ } elseif ($prefix === 'QUIT') {
+ $this->close();
+ }
+
+ return true;
+ }
+
+ private function close()
+ {
+ $this->extensions = null;
+ if ($this->connection) {
+ fclose($this->connection);
+ $this->connection = null;
+ $this->debug('Connection: closed');
+ }
+ }
+
+ private function parseEhloResponse($str)
+ {
+ $ary = explode("\n", trim(str_replace(["\r\n", "\r"], "\n", $str)));
+ unset($ary[0]);
+ foreach($ary as $ext) {
+ $v = explode(" ", substr($ext, 4));
+ $key = $v[0];
+ if (isset($v[1])) {
+ unset($v[0]);
+ $this->extensions[$key] = $v[1];
+ } else {
+ $this->extensions[$key] = true;
+ }
+ }
+ }
+
+ private function debug($str)
+ {
+ if (!$this->debug) {
+ return;
+ }
+ $str = preg_replace('/\r\n|\r/ms', "\n", $str);
+ echo "[", date('Y-m-d H:i:s'), "] ", trim(str_replace("\n","\n" . str_repeat("\t", 4), trim($str))), "\n";
+ }
+
+ private function valid($address)
+ {
+ $address = strtolower($address);
+ $address = filter_var($address, FILTER_SANITIZE_EMAIL);
+ if (!filter_var($address, FILTER_VALIDATE_EMAIL)) {
+ $this->debug('Invalid address: '. $address);
+ return false;
+ }
+ return $address;
+ }
+
+ public function setTimeout($seconds = 30)
+ {
+ $this->timeout = (int) $seconds;
+ }
+
+ public function getTimeout()
+ {
+ return $this->timeout;
+ }
+
+ public static function stripnl($str)
+ {
+ return trim(str_replace(["\r", "\n"], '', $str));
+ }
+
+ private function formatAddress($address)
+ {
+ if (empty($address[1])) {
+ return $address[0];
+ }
+ return '"'. preg_replace('#^:\s+#', '', iconv_mime_encode('', $address[1])) .'" <'.$address[0].'>';
+ }
+
+ private function concatAddress($addresses)
+ {
+ $list = [];
+ foreach ($addresses as $address) {
+ $list[] = $this->formatAddress($address);
+ }
+ return implode(', ', $list);
+ }
+
+ private function generateMessageId()
+ {
+ $bytes = random_bytes(16);
+ $bytes[6] = chr(ord($bytes[6]) & 0x0f | 0x40);
+ $bytes[8] = chr(ord($bytes[8]) & 0x3f | 0x80);
+ return '<' . vsprintf('%s%s-%s-%s-%s-%s%s%s', str_split(bin2hex($bytes), 4)) . '@' . $this->hostname .'>';
+ }
+
+ public function html2text($html)
+ {
+ $html = html_entity_decode($html, ENT_QUOTES, $this->charset);
+ $html = strip_tags(preg_replace('/<(head|title|style|script)[^>]*>.*?<\/\\1>/si', '', $html), '
');
+ return trim(str_replace('
', "\n", $html));
+ }
+}
\ No newline at end of file