Advertisement
ArcaniSGK507

Untitled

Mar 27th, 2025
340
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
PHP 10.12 KB | None | 0 0
  1. <?php
  2.  
  3. namespace App\Services;
  4.  
  5. use Exception;
  6. use PHPMailer\PHPMailer\PHPMailer;
  7. use PHPMailer\PHPMailer\Exception as PHPMailerException;
  8. use PHPMailer\PHPMailer\SMTP;
  9. use App\Services\Email\EmailTemplateRenderer;
  10. use App\Services\Email\HtmlValidator;
  11. use App\Services\Email\EmailRateLimiter;
  12. use App\Services\Email\EmailSecurityManager;
  13.  
  14. /**
  15.  * Email Service
  16.  *
  17.  * Core email service that handles basic email sending functionality.
  18.  *
  19.  * @package App\Services
  20.  */
  21. class EmailService
  22. {
  23.     /**
  24.      * @var PHPMailer PHPMailer instance
  25.      */
  26.     private PHPMailer $mailer;
  27.  
  28.     /**
  29.      * @var Logger Logger service
  30.      */
  31.     private Logger $logger;
  32.  
  33.     /**
  34.      * @var SecurityService Security service
  35.      */
  36.     private SecurityService $securityService;
  37.  
  38.     /**
  39.      * @var array Default email configuration
  40.      */
  41.     private array $config;
  42.  
  43.     /**
  44.      * @var EmailTemplateRenderer Template renderer
  45.      */
  46.     private EmailTemplateRenderer $templateRenderer;
  47.  
  48.     /**
  49.      * @var HtmlValidator HTML validator
  50.      */
  51.     private HtmlValidator $htmlValidator;
  52.  
  53.     /**
  54.      * @var EmailRateLimiter Rate limiter
  55.      */
  56.     private EmailRateLimiter $rateLimiter;
  57.  
  58.     /**
  59.      * @var EmailSecurityManager Security manager
  60.      */
  61.     private EmailSecurityManager $securityManager;
  62.  
  63.     /**
  64.      * EmailService constructor
  65.      *
  66.      * @param array $config Custom email configuration
  67.      * @param int $maxEmailsPerHour Maximum emails per hour for rate limiting
  68.      * @throws PHPMailerException
  69.      */
  70.     public function __construct(array $config = [], int $maxEmailsPerHour = 10)
  71.     {
  72.         $this->logger = new Logger();
  73.         $this->securityService = new SecurityService();
  74.         $this->templateRenderer = new EmailTemplateRenderer();
  75.         $this->htmlValidator = new HtmlValidator();
  76.         $this->rateLimiter = new EmailRateLimiter($maxEmailsPerHour);
  77.         $this->securityManager = new EmailSecurityManager();
  78.  
  79.         // Default configuration from constants
  80.         $this->config = [
  81.             'host' => MAIL_HOST,
  82.             'port' => MAIL_PORT,
  83.             'username' => MAIL_USERNAME,
  84.             'password' => MAIL_PASSWORD,
  85.             'encryption' => MAIL_ENCRYPTION,
  86.             'from_address' => MAIL_FROM_ADDRESS,
  87.             'from_name' => MAIL_FROM_NAME,
  88.             'debug' => APP_DEBUG ? 1 : 0,
  89.         ];
  90.  
  91.         // Override with custom config if provided
  92.         if (!empty($config)) {
  93.             $this->config = array_merge($this->config, $config);
  94.         }
  95.  
  96.         $this->initializeMailer();
  97.     }
  98.  
  99.     /**
  100.      * Initialize PHPMailer with config settings
  101.      *
  102.      * @return void
  103.      * @throws PHPMailerException
  104.      */
  105.     private function initializeMailer(): void
  106.     {
  107.         $this->mailer = new PHPMailer(true);
  108.  
  109.         // Server settings
  110.         $this->mailer->isSMTP();
  111.         $this->mailer->Host = $this->config['host'];
  112.         $this->mailer->SMTPAuth = true;
  113.         $this->mailer->Username = $this->config['username'];
  114.         $this->mailer->Password = $this->config['password'];
  115.         $this->mailer->SMTPSecure = $this->config['encryption'];
  116.         $this->mailer->Port = $this->config['port'];
  117.         $this->mailer->CharSet = 'UTF-8';
  118.  
  119.         // Debug settings
  120.         if (APP_ENV === 'development' || $this->config['debug'] > 0) {
  121.             // Ensure log directory exists
  122.             $logDir = ROOT_DIR . '/logs/track-mail';
  123.             if (!is_dir($logDir)) {
  124.                 mkdir($logDir, 0755, true);
  125.             }
  126.  
  127.             // Generate log file name with timestamp
  128.             $timestamp = date('Y-m-d_H-i-s');
  129.             $logFile = $logDir . '/mail_' . $timestamp . '.log';
  130.  
  131.             // Set debug level
  132.             $this->mailer->SMTPDebug = SMTP::DEBUG_SERVER;
  133.  
  134.             // Custom debug output handler that writes to log file
  135.             $this->mailer->Debugoutput = function ($str, $level) use ($logFile) {
  136.                 // Append to log file
  137.                 file_put_contents($logFile, $str . PHP_EOL, FILE_APPEND);
  138.             };
  139.         } else {
  140.             $this->mailer->SMTPDebug = 0; // Turn off debugging in production
  141.         }
  142.  
  143.         // Default sender
  144.         $this->mailer->setFrom(
  145.             $this->config['from_address'],
  146.             $this->config['from_name']
  147.         );
  148.  
  149.         // Set default reply-to
  150.         $this->mailer->addReplyTo(
  151.             $this->config['from_address'],
  152.             $this->config['from_name']
  153.         );
  154.     }
  155.  
  156.     /**
  157.      * Send an email
  158.      *
  159.      * @param string|array $to Recipient(s)
  160.      * @param string $subject Email subject
  161.      * @param string $body Email body (HTML)
  162.      * @param string|null $plainText Plain text alternative
  163.      * @param array $attachments Attachments [path => name]
  164.      * @param array $cc CC recipients [email => name]
  165.      * @param array $bcc BCC recipients [email => name]
  166.      * @return bool True if email was sent successfully
  167.      * @throws Exception If email sending fails
  168.      */
  169.     public function send(
  170.         string|array $to,
  171.         string       $subject,
  172.         string       $body,
  173.         ?string      $plainText = null,
  174.         array        $attachments = [],
  175.         array        $cc = [],
  176.         array        $bcc = []
  177.     ): bool
  178.     {
  179.         try {
  180.             // Check if we're being rate limited
  181.             if (!$this->rateLimiter->checkRateLimit()) {
  182.                 $this->logger->warning('Email rate limit exceeded', [
  183.                     'to' => $to,
  184.                     'subject' => $subject
  185.                 ]);
  186.                 throw new Exception('Email sending rate limit exceeded. Please try again later.');
  187.             }
  188.  
  189.             // Log email attempt
  190.             $this->rateLimiter->logEmailAttempt($to, $subject);
  191.  
  192.             // Reset recipients
  193.             $this->mailer->clearAllRecipients();
  194.             $this->mailer->clearAttachments();
  195.  
  196.             // Add recipient(s)
  197.             if (is_array($to)) {
  198.                 foreach ($to as $email => $name) {
  199.                     if (is_numeric($email)) {
  200.                         $this->mailer->addAddress($name); // If just email without name
  201.                     } else {
  202.                         $this->mailer->addAddress($email, $name);
  203.                     }
  204.                 }
  205.             } else {
  206.                 $this->mailer->addAddress($to);
  207.             }
  208.  
  209.             // Add CC recipients
  210.             foreach ($cc as $email => $name) {
  211.                 if (is_numeric($email)) {
  212.                     $this->mailer->addCC($name); // If just email without name
  213.                 } else {
  214.                     $this->mailer->addCC($email, $name);
  215.                 }
  216.             }
  217.  
  218.             // Add BCC recipients
  219.             foreach ($bcc as $email => $name) {
  220.                 if (is_numeric($email)) {
  221.                     $this->mailer->addBCC($name); // If just email without name
  222.                 } else {
  223.                     $this->mailer->addBCC($email, $name);
  224.                 }
  225.             }
  226.  
  227.             // Add attachments
  228.             foreach ($attachments as $path => $name) {
  229.                 $this->mailer->addAttachment($path, $name);
  230.             }
  231.  
  232.             // Set email content
  233.             $this->mailer->isHTML(true);
  234.             $this->mailer->Subject = $subject;
  235.  
  236.             $body = $this->htmlValidator->validate($body);
  237.  
  238.             $this->mailer->Body = $body;
  239.  
  240.             // Set plain text alternative if provided
  241.             if ($plainText !== null) {
  242.                 $this->mailer->AltBody = $plainText;
  243.             } else {
  244.                 // Generate plain text from HTML
  245.                 $this->mailer->AltBody = $this->templateRenderer->htmlToPlainText($body);
  246.             }
  247.  
  248.             // Add security headers to prevent email spoofing
  249.             $this->securityManager->addSecurityHeaders($this->mailer);
  250.  
  251.             // Send the email
  252.             $result = $this->mailer->send();
  253.  
  254.             // Log successful sending
  255.             $this->logger->info('Email sent successfully', [
  256.                 'to' => $to,
  257.                 'subject' => $subject
  258.             ]);
  259.  
  260.             return $result;
  261.         } catch (PHPMailerException $e) {
  262.             // Log the error
  263.             $this->logger->error('Email sending failed', [
  264.                 'error' => $e->getMessage(),
  265.                 'to' => $to,
  266.                 'subject' => $subject
  267.             ]);
  268.  
  269.             // Log security event
  270.             $this->securityService->logSecurityEvent(
  271.                 'email_error',
  272.                 [
  273.                     'error' => $e->getMessage(),
  274.                     'to' => is_array($to) ? implode(', ', array_keys($to)) : $to,
  275.                     'subject' => $subject
  276.                 ],
  277.                 'warning'
  278.             );
  279.  
  280.             throw new Exception('Failed to send email: ' . $e->getMessage());
  281.         }
  282.     }
  283.  
  284.     /**
  285.      * Reset mailer instance with fresh configuration
  286.      *
  287.      * @return void
  288.      * @throws PHPMailerException
  289.      */
  290.     public function resetMailer(): void
  291.     {
  292.         $this->initializeMailer();
  293.     }
  294.  
  295.     /**
  296.      * Get the PHPMailer instance for advanced configuration
  297.      *
  298.      * @return PHPMailer PHPMailer instance
  299.      */
  300.     public function getMailer(): PHPMailer
  301.     {
  302.         return $this->mailer;
  303.     }
  304.  
  305.     /**
  306.      * Set a custom reply-to address
  307.      *
  308.      * @param string $email Reply-to email
  309.      * @param string $name Reply-to name
  310.      * @return void
  311.      * @throws PHPMailerException
  312.      */
  313.     public function setReplyTo(string $email, string $name = ''): void
  314.     {
  315.         $this->mailer->clearReplyTos();
  316.         $this->mailer->addReplyTo($email, $name);
  317.     }
  318.  
  319.     /**
  320.      * Get the template renderer
  321.      *
  322.      * @return EmailTemplateRenderer
  323.      */
  324.     public function getTemplateRenderer(): EmailTemplateRenderer
  325.     {
  326.         return $this->templateRenderer;
  327.     }
  328.  
  329.     /**
  330.      * Get the HTML validator
  331.      *
  332.      * @return HtmlValidator
  333.      */
  334.     public function getHtmlValidator(): HtmlValidator
  335.     {
  336.         return $this->htmlValidator;
  337.     }
  338. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement