2020-06-27 22:47:12 +02:00
< ? php
namespace Api {
2021-04-09 16:05:36 +02:00
use Objects\ConnectionData ;
2020-07-01 21:10:25 +02:00
abstract class MailAPI extends Request {
2021-04-09 16:05:36 +02:00
protected function getMailConfig () : ? ConnectionData {
$req = new \Api\Settings\Get ( $this -> user );
$this -> success = $req -> execute ( array ( " key " => " ^mail_ " ));
$this -> lastError = $req -> getLastError ();
if ( $this -> success ) {
$settings = $req -> getResult ()[ " settings " ];
if ( ! isset ( $settings [ " mail_enabled " ]) || $settings [ " mail_enabled " ] !== " 1 " ) {
$this -> createError ( " Mail is not configured yet. " );
return null ;
}
$host = $settings [ " mail_host " ] ? ? " localhost " ;
$port = intval ( $settings [ " mail_port " ] ? ? " 25 " );
$login = $settings [ " mail_username " ] ? ? " " ;
$password = $settings [ " mail_password " ] ? ? " " ;
$connectionData = new ConnectionData ( $host , $port , $login , $password );
$connectionData -> setProperty ( " from " , $settings [ " mail_from " ] ? ? " " );
$connectionData -> setProperty ( " last_sync " , $settings [ " mail_last_sync " ] ? ? " " );
return $connectionData ;
}
2020-06-27 22:47:12 +02:00
2021-04-09 16:05:36 +02:00
return null ;
}
2020-06-27 22:47:12 +02:00
}
}
namespace Api\Mail {
use Api\MailAPI ;
use Api\Parameter\Parameter ;
use Api\Parameter\StringType ;
2021-04-09 16:05:36 +02:00
use Driver\SQL\Column\Column ;
use Driver\SQL\Condition\Compare ;
2021-04-10 00:44:34 +02:00
use Driver\SQL\Condition\CondIn ;
2021-04-09 16:05:36 +02:00
use Driver\SQL\Strategy\UpdateStrategy ;
2020-06-27 22:47:12 +02:00
use External\PHPMailer\Exception ;
use External\PHPMailer\PHPMailer ;
2021-04-09 20:59:36 +02:00
use Objects\ConnectionData ;
2020-06-27 22:47:12 +02:00
use Objects\User ;
class Test extends MailAPI {
public function __construct ( User $user , bool $externalCall = false ) {
parent :: __construct ( $user , $externalCall , array (
" receiver " => new Parameter ( " receiver " , Parameter :: TYPE_EMAIL )
));
}
2021-04-02 21:58:06 +02:00
public function execute ( $values = array ()) : bool {
2020-06-27 22:47:12 +02:00
if ( ! parent :: execute ( $values )) {
return false ;
}
$receiver = $this -> getParam ( " receiver " );
$req = new \Api\Mail\Send ( $this -> user );
$this -> success = $req -> execute ( array (
" to " => $receiver ,
" subject " => " Test E-Mail " ,
" body " => " Hey! If you receive this e-mail, your mail configuration seems to be working. "
));
$this -> lastError = $req -> getLastError ();
return $this -> success ;
}
}
class Send extends MailAPI {
public function __construct ( $user , $externalCall = false ) {
parent :: __construct ( $user , $externalCall , array (
2021-04-09 16:05:36 +02:00
'to' => new Parameter ( 'to' , Parameter :: TYPE_EMAIL , true , null ),
'subject' => new StringType ( 'subject' , - 1 ),
2020-06-27 22:47:12 +02:00
'body' => new StringType ( 'body' , - 1 ),
2021-04-09 16:05:36 +02:00
'replyTo' => new Parameter ( 'replyTo' , Parameter :: TYPE_EMAIL , true , null ),
'replyName' => new StringType ( 'replyName' , 32 , true , " " )
2020-06-27 22:47:12 +02:00
));
$this -> isPublic = false ;
}
2021-04-02 21:58:06 +02:00
public function execute ( $values = array ()) : bool {
2021-04-09 16:05:36 +02:00
if ( ! parent :: execute ( $values )) {
2020-06-27 22:47:12 +02:00
return false ;
}
$mailConfig = $this -> getMailConfig ();
if ( ! $this -> success ) {
return false ;
}
2021-04-09 16:05:36 +02:00
$fromMail = $mailConfig -> getProperty ( 'from' );
$toMail = $this -> getParam ( 'to' ) ? ? $fromMail ;
$subject = $this -> getParam ( 'subject' );
$replyTo = $this -> getParam ( 'replyTo' );
$replyName = $this -> getParam ( 'replyName' );
2020-06-27 22:47:12 +02:00
try {
$mail = new PHPMailer ;
$mail -> IsSMTP ();
2021-04-09 16:05:36 +02:00
$mail -> setFrom ( $fromMail );
$mail -> addAddress ( $toMail );
if ( $replyTo ) {
$mail -> addReplyTo ( $replyTo , $replyName );
}
$mail -> Subject = $subject ;
2020-06-27 22:47:12 +02:00
$mail -> SMTPDebug = 0 ;
$mail -> Host = $mailConfig -> getHost ();
$mail -> Port = $mailConfig -> getPort ();
$mail -> SMTPAuth = true ;
$mail -> Username = $mailConfig -> getLogin ();
$mail -> Password = $mailConfig -> getPassword ();
$mail -> SMTPSecure = 'tls' ;
$mail -> IsHTML ( true );
$mail -> CharSet = 'UTF-8' ;
$mail -> Body = $this -> getParam ( 'body' );
$this -> success = @ $mail -> Send ();
if ( ! $this -> success ) {
$this -> lastError = " Error sending Mail: $mail->ErrorInfo " ;
error_log ( " sendMail() failed: $mail->ErrorInfo " );
2021-04-09 16:05:36 +02:00
} else {
$this -> result [ " messageId " ] = $mail -> getLastMessageID ();
2020-06-27 22:47:12 +02:00
}
} catch ( Exception $e ) {
$this -> success = false ;
$this -> lastError = " Error sending Mail: $e " ;
}
return $this -> success ;
}
}
2021-04-09 16:05:36 +02:00
2021-04-09 20:59:36 +02:00
// TODO: IMAP mail settings :(
2021-04-10 00:44:34 +02:00
// TODO: attachments
2021-04-09 16:05:36 +02:00
class Sync extends MailAPI {
public function __construct ( User $user , bool $externalCall = false ) {
parent :: __construct ( $user , $externalCall , array ());
2021-04-10 00:44:34 +02:00
$this -> loginRequired = true ;
2021-04-09 16:05:36 +02:00
}
private function fetchMessageIds () {
$sql = $this -> user -> getSQL ();
$res = $sql -> select ( " uid " , " messageId " )
-> from ( " ContactRequest " )
-> where ( new Compare ( " messageId " , NULL , " != " ))
-> execute ();
$this -> success = ( $res !== false );
$this -> lastError = $sql -> getLastError ();
if ( ! $this -> success ) {
return false ;
}
$messageIds = [];
foreach ( $res as $row ) {
$messageIds [ $row [ " messageId " ]] = $row [ " uid " ];
}
return $messageIds ;
}
private function findContactRequest ( array & $messageIds , array & $references ) : ? int {
foreach ( $references as & $ref ) {
if ( isset ( $messageIds [ $ref ])) {
return $messageIds [ $ref ];
}
}
return null ;
}
private function parseBody ( string $body ) : string {
// TODO clean this up
return trim ( $body );
}
private function insertMessages ( $messages ) : bool {
$sql = $this -> user -> getSQL ();
2021-04-10 00:44:34 +02:00
$query = $sql -> insert ( " ContactMessage " , [ " request_id " , " user_id " , " message " , " messageId " , " created_at " ])
2021-04-09 16:05:36 +02:00
-> onDuplicateKeyStrategy ( new UpdateStrategy ([ " message_id " ], [ " message " => new Column ( " message " )]));
2021-04-10 00:44:34 +02:00
$entityIds = [];
2021-04-09 16:05:36 +02:00
foreach ( $messages as $message ) {
2021-04-10 00:44:34 +02:00
$requestId = $message [ " requestId " ];
2021-04-09 16:05:36 +02:00
$query -> addRow (
2021-04-10 00:44:34 +02:00
$requestId ,
2021-04-09 16:05:36 +02:00
$sql -> select ( " uid " ) -> from ( " User " ) -> where ( new Compare ( " email " , $message [ " from " ])) -> limit ( 1 ),
$message [ " body " ],
2021-04-10 00:44:34 +02:00
$message [ " messageId " ],
( new \DateTime ()) -> setTimeStamp ( $message [ " timestamp " ]),
2021-04-09 16:05:36 +02:00
);
2021-04-10 00:44:34 +02:00
if ( ! in_array ( $requestId , $entityIds )) {
$entityIds [] = $requestId ;
}
2021-04-09 16:05:36 +02:00
}
$this -> success = $query -> execute ();
$this -> lastError = $sql -> getLastError ();
2021-04-10 00:44:34 +02:00
// Update entity log
if ( $this -> success && count ( $entityIds ) > 0 ) {
$sql -> update ( " EntityLog " )
-> set ( " modified " , $sql -> now ())
-> where ( new CondIn ( " entityId " , $entityIds ))
-> execute ();
}
2021-04-09 16:05:36 +02:00
return $this -> success ;
}
2021-04-09 16:30:49 +02:00
private function parseDate ( $date ) {
2021-04-09 20:59:36 +02:00
$formats = [ null , " D M d Y H:i:s e+ " , " D, j M Y H:i:s e+ " ];
2021-04-09 16:30:49 +02:00
foreach ( $formats as $format ) {
try {
2021-04-09 20:59:36 +02:00
$dateObj = ( $format === null ? new \DateTime ( $date ) : \DateTime :: createFromFormat ( $format , $date ));
if ( $dateObj ) {
return $dateObj ;
}
2021-04-09 16:30:49 +02:00
} catch ( \Exception $exception ) {
}
}
return $this -> createError ( " Could not parse date: $date " );
}
2021-04-09 20:59:36 +02:00
private function getReference ( ConnectionData $mailConfig ) : string {
2021-04-09 16:05:36 +02:00
$port = 993 ;
$host = str_replace ( " smtp " , " imap " , $mailConfig -> getHost ());
$flags = [ " /ssl " ];
2021-04-09 20:59:36 +02:00
return '{' . $host . ':' . $port . implode ( " " , $flags ) . '}' ;
}
private function connect ( ConnectionData $mailConfig ) {
2021-04-09 16:05:36 +02:00
2021-04-09 20:59:36 +02:00
$username = $mailConfig -> getLogin ();
$password = $mailConfig -> getPassword ();
$ref = $this -> getReference ( $mailConfig );
$mbox = @ imap_open ( $ref , $username , $password , OP_READONLY );
2021-04-09 16:05:36 +02:00
if ( ! $mbox ) {
return $this -> createError ( " Can't connect to mail server via IMAP: " . imap_last_error ());
}
2021-04-09 20:59:36 +02:00
return $mbox ;
}
private function listFolders ( ConnectionData $mailConfig , $mbox ) {
$boxes = @ imap_list ( $mbox , $this -> getReference ( $mailConfig ), '*' );
if ( ! $boxes ) {
return $this -> createError ( " Error listing imap folders: " . imap_last_error ());
2021-04-09 16:05:36 +02:00
}
2021-04-09 20:59:36 +02:00
return $boxes ;
}
2021-04-10 00:44:34 +02:00
private function getSenderAddress ( $header ) : string {
if ( property_exists ( $header , " reply_to " ) && count ( $header -> reply_to ) > 0 ) {
$mailBox = $header -> reply_to [ 0 ] -> mailbox ;
$host = $header -> reply_to [ 0 ] -> host ;
} else if ( property_exists ( $header , " from " ) && count ( $header -> from ) > 0 ) {
$mailBox = $header -> from [ 0 ] -> mailbox ;
$host = $header -> from [ 0 ] -> host ;
} else {
return " unknown_addr " ;
}
return " $mailBox @ $host " ;
}
2021-04-09 21:03:43 +02:00
private function runSearch ( $mbox , string $searchCriteria , ? \DateTime $lastSyncDateTime , array $messageIds , array & $messages ) {
2021-04-09 16:05:36 +02:00
$result = @ imap_search ( $mbox , $searchCriteria );
if ( $result === false ) {
2021-04-09 21:03:43 +02:00
$err = imap_last_error (); // might return false, if not messages were found, so we can just abort without throwing an error
return empty ( $err ) ? true : $this -> createError ( " Could not run search: $err " );
2021-04-09 16:05:36 +02:00
}
foreach ( $result as $msgNo ) {
$header = imap_headerinfo ( $mbox , $msgNo );
2021-04-09 16:30:49 +02:00
$date = $this -> parseDate ( $header -> date );
if ( $date === false ) {
return false ;
}
2021-04-09 20:59:36 +02:00
if ( $lastSyncDateTime === null || \datetimeDiff ( $lastSyncDateTime , $date ) > 0 ) {
2021-04-09 16:05:36 +02:00
$references = property_exists ( $header , " references " ) ?
explode ( " " , $header -> references ) : [];
$requestId = $this -> findContactRequest ( $messageIds , $references );
if ( $requestId ) {
$messageId = $header -> message_id ;
2021-04-10 00:44:34 +02:00
$senderAddress = $this -> getSenderAddress ( $header );
2021-04-09 16:05:36 +02:00
$structure = imap_fetchstructure ( $mbox , $msgNo );
$attachments = [];
$hasAttachments = ( property_exists ( $structure , " parts " ));
if ( $hasAttachments ) {
foreach ( $structure -> parts as $part ) {
$disposition = ( property_exists ( $part , " disposition " ) ? $part -> disposition : null );
if ( $disposition === " attachment " ) {
$fileName = array_filter ( $part -> dparameters , function ( $param ) { return $param -> attribute === " filename " ; });
if ( count ( $fileName ) > 0 ) {
$attachments [] = $fileName [ 0 ] -> value ;
}
}
}
}
$body = imap_fetchbody ( $mbox , $msgNo , " 1 " );
$body = $this -> parseBody ( $body );
$messages [] = [
" messageId " => $messageId ,
" requestId " => $requestId ,
" timestamp " => $date -> getTimestamp (),
" from " => $senderAddress ,
" body " => $body ,
" attachments " => $attachments
];
}
}
}
2021-04-09 20:59:36 +02:00
return true ;
}
public function execute ( $values = array ()) : bool {
if ( ! parent :: execute ( $values )) {
return false ;
}
if ( ! function_exists ( " imap_open " )) {
return $this -> createError ( " IMAP is not enabled. Enable it inside the php config. For more information visit: https://www.php.net/manual/en/imap.setup.php " );
}
$messageIds = $this -> fetchMessageIds ();
if ( $messageIds === false ) {
return false ;
} else if ( count ( $messageIds ) === 0 ) {
// nothing to sync here
return true ;
}
$mailConfig = $this -> getMailConfig ();
if ( ! $this -> success ) {
return false ;
}
$mbox = $this -> connect ( $mailConfig );
if ( $mbox === false ) {
return false ;
}
$boxes = $this -> listFolders ( $mailConfig , $mbox );
if ( $boxes === false ) {
return false ;
}
$now = ( new \DateTime ()) -> getTimestamp ();
$lastSync = intval ( $mailConfig -> getProperty ( " last_sync " , " 0 " ));
if ( $lastSync > 0 ) {
$lastSyncDateTime = ( new \DateTime ()) -> setTimeStamp ( $lastSync );
$dateStr = $lastSyncDateTime -> format ( " d-M-Y " );
$searchCriteria = " SINCE \" $dateStr\ " " ;
} else {
$lastSyncDateTime = null ;
$searchCriteria = " ALL " ;
}
$messages = [];
foreach ( $boxes as $box ) {
imap_reopen ( $mbox , $box );
2021-04-09 21:03:43 +02:00
if ( ! $this -> runSearch ( $mbox , $searchCriteria , $lastSyncDateTime , $messageIds , $messages )) {
2021-04-09 20:59:36 +02:00
return false ;
}
}
2021-04-09 16:05:36 +02:00
@ imap_close ( $mbox );
2021-04-09 16:30:49 +02:00
if ( ! empty ( $messages ) && ! $this -> insertMessages ( $messages )) {
2021-04-09 16:05:36 +02:00
return false ;
}
$req = new \Api\Settings\Set ( $this -> user );
$this -> success = $req -> execute ( array ( " settings " => array ( " mail_last_sync " => " $now " )));
$this -> lastError = $req -> getLastError ();
return $this -> success ;
}
}
2020-06-27 22:47:12 +02:00
}