diff --git a/.claude/settings.local.json b/.claude/settings.local.json index bdf13d0..20fffeb 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -14,7 +14,24 @@ "PowerShell(docker exec php-mvc-territory-app-1 cat /var/www/html/vendor/cartalyst/sentinel/src/Native/Facades/Sentinel.php 2>&1)", "PowerShell(docker exec php-mvc-territory-app-1 cat /var/www/html/vendor/cartalyst/sentinel/src/Native/ConfigRepository.php 2>&1)", "PowerShell(docker exec php-mvc-territory-app-1 cat /var/www/html/vendor/cartalyst/sentinel/src/config/config.php 2>&1)", - "Bash(docker-compose exec app bash -c ' *)" + "Bash(docker-compose exec app bash -c ' *)", + "Bash(Select-Object Name, PSIsContainer)", + "PowerShell(Get-ChildItem -Path \"c:\\\\Development\\\\PHP\\\\PHP-MVC-TERRITORY\\\\bootstrap\" | ForEach-Object { $_.Name })", + "PowerShell(Get-ChildItem -Path \"c:\\\\Development\\\\PHP\\\\PHP-MVC-TERRITORY\\\\app\" | ForEach-Object { $_.Name })", + "PowerShell(Get-ChildItem -Path \"c:\\\\Development\\\\PHP\\\\PHP-MVC-TERRITORY\\\\config\" | ForEach-Object { $_.Name })", + "PowerShell(Get-ChildItem -Path \"c:\\\\Development\\\\PHP\\\\PHP-MVC-TERRITORY\\\\app\\\\Controllers\" | ForEach-Object { $_.Name })", + "PowerShell(Get-ChildItem -Path \"c:\\\\Development\\\\PHP\\\\PHP-MVC-TERRITORY\\\\app\\\\Models\" | ForEach-Object { $_.Name })", + "PowerShell(Get-ChildItem -Path \"c:\\\\Development\\\\PHP\\\\PHP-MVC-TERRITORY\\\\app\\\\Views\" | ForEach-Object { $_.Name })", + "PowerShell(Get-ChildItem -Path \"c:\\\\Development\\\\PHP\\\\PHP-MVC-TERRITORY\\\\core\" | ForEach-Object { $_.Name })", + "PowerShell(Get-ChildItem -Path \"c:\\\\Development\\\\PHP\\\\PHP-MVC-TERRITORY\\\\routes\" | ForEach-Object { $_.Name })", + "PowerShell(Get-ChildItem -Path \"c:\\\\Development\\\\PHP\\\\PHP-MVC-TERRITORY\\\\public\" | ForEach-Object { $_.Name })", + "PowerShell(Get-ChildItem -Path \"c:\\\\Development\\\\PHP\\\\PHP-MVC-TERRITORY\\\\app\\\\Repositories\" | ForEach-Object { $_.Name })", + "PowerShell(Get-ChildItem -Path \"c:\\\\Development\\\\PHP\\\\PHP-MVC-TERRITORY\\\\app\\\\ViewModels\" | ForEach-Object { $_.Name })", + "PowerShell(Get-ChildItem -Path \"c:\\\\Development\\\\PHP\\\\PHP-MVC-TERRITORY\\\\database\" | ForEach-Object { $_.Name })", + "PowerShell(Get-ChildItem -Path \"c:\\\\Development\\\\PHP\\\\PHP-MVC-TERRITORY\\\\database\\\\migrations\" | ForEach-Object { $_.Name })", + "Bash(composer require *)", + "PowerShell(Get-Command php -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source; Get-ChildItem \"C:\\\\\", \"C:\\\\php\", \"C:\\\\xampp\", \"C:\\\\wamp\" -ErrorAction SilentlyContinue -Depth 1 | Where-Object { $_.Name -match \"composer|php\" })", + "PowerShell($env:PATH -split \";\" | Where-Object { $_ -match \"php|laragon|xampp|wamp\" })" ] } } diff --git a/bootstrap/mail.php b/bootstrap/mail.php new file mode 100644 index 0000000..1006203 --- /dev/null +++ b/bootstrap/mail.php @@ -0,0 +1,18 @@ +=5.5.0" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^1.0", + "doctrine/annotations": "^1.2.6 || ^1.13.3", + "php-parallel-lint/php-console-highlighter": "^1.0.0", + "php-parallel-lint/php-parallel-lint": "^1.3.2", + "phpcompatibility/php-compatibility": "^10.0.0@dev", + "squizlabs/php_codesniffer": "^3.13.5", + "yoast/phpunit-polyfills": "^1.0.4" + }, + "suggest": { + "decomplexity/SendOauth2": "Adapter for using XOAUTH2 authentication", + "directorytree/imapengine": "For uploading sent messages via IMAP, see gmail example", + "ext-imap": "Needed to support advanced email address parsing according to RFC822", + "ext-mbstring": "Needed to send email in multibyte encoding charset or decode encoded addresses", + "ext-openssl": "Needed for secure SMTP sending and DKIM signing", + "greew/oauth2-azure-provider": "Needed for Microsoft Azure XOAUTH2 authentication", + "hayageek/oauth2-yahoo": "Needed for Yahoo XOAUTH2 authentication", + "league/oauth2-google": "Needed for Google XOAUTH2 authentication", + "psr/log": "For optional PSR-3 debug logging", + "symfony/polyfill-mbstring": "To support UTF-8 if the Mbstring PHP extension is not enabled (^1.2)", + "thenetworg/oauth2-azure": "Needed for Microsoft XOAUTH2 authentication" + }, + "type": "library", + "autoload": { + "psr-4": { + "PHPMailer\\PHPMailer\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "LGPL-2.1-only" + ], + "authors": [ + { + "name": "Marcus Bointon", + "email": "phpmailer@synchromedia.co.uk" + }, + { + "name": "Jim Jagielski", + "email": "jimjag@gmail.com" + }, + { + "name": "Andy Prevost", + "email": "codeworxtech@users.sourceforge.net" + }, + { + "name": "Brent R. Matzelle" + } + ], + "description": "PHPMailer is a full-featured email creation and transfer class for PHP", + "support": { + "issues": "https://github.com/PHPMailer/PHPMailer/issues", + "source": "https://github.com/PHPMailer/PHPMailer/tree/v7.1.1" + }, + "funding": [ + { + "url": "https://github.com/Synchro", + "type": "github" + } + ], + "time": "2026-05-18T08:06:14+00:00" + }, { "name": "psr/clock", "version": "1.0.0", diff --git a/config/mail.php b/config/mail.php new file mode 100644 index 0000000..8ce3062 --- /dev/null +++ b/config/mail.php @@ -0,0 +1,14 @@ + $_ENV['MAIL_HOST'] ?? 'smtp.gmail.com', + 'port' => (int) ($_ENV['MAIL_PORT'] ?? 587), + 'username' => $_ENV['MAIL_USERNAME'] ?? '', + 'password' => $_ENV['MAIL_PASSWORD'] ?? '', + 'encryption' => $_ENV['MAIL_ENCRYPTION'] ?? 'tls', + 'from_email' => $_ENV['MAIL_FROM_EMAIL'] ?? '', + 'from_name' => $_ENV['MAIL_FROM_NAME'] ?? 'PHP MVC Territory', + 'debug' => (int) ($_ENV['MAIL_DEBUG'] ?? 0), +]; diff --git a/core/Mailer.php b/core/Mailer.php new file mode 100644 index 0000000..d16def6 --- /dev/null +++ b/core/Mailer.php @@ -0,0 +1,58 @@ +config = require __DIR__ . '/../config/mail.php'; + } + + /** + * Send an email. + * + * @param string $toEmail + * @param string $toName + * @param string $subject + * @param string $htmlBody + * @param string $textBody Plain-text fallback (optional) + * @throws MailerException + */ + public function send( + string $toEmail, + string $toName, + string $subject, + string $htmlBody, + string $textBody = '' + ): void { + $mail = new PHPMailer(true); + + $mail->isSMTP(); + $mail->Host = $this->config['host']; + $mail->SMTPAuth = true; + $mail->Username = $this->config['username']; + $mail->Password = $this->config['password']; + $mail->SMTPSecure = $this->config['encryption'] === 'ssl' ? PHPMailer::ENCRYPTION_SMTPS : PHPMailer::ENCRYPTION_STARTTLS; + $mail->Port = $this->config['port']; + $mail->SMTPDebug = $this->config['debug']; + + $mail->setFrom($this->config['from_email'], $this->config['from_name']); + $mail->addAddress($toEmail, $toName); + + $mail->isHTML(true); + $mail->Subject = $subject; + $mail->Body = $htmlBody; + $mail->AltBody = $textBody ?: strip_tags($htmlBody); + + $mail->send(); + } +} diff --git a/public/index.php b/public/index.php index eceeea1..19f61cc 100644 --- a/public/index.php +++ b/public/index.php @@ -6,14 +6,17 @@ require_once __DIR__ . '/../vendor/autoload.php'; use Core\App; use Core\Dispatcher; +use Core\Mailer; use Core\Request; use Core\Router; ensureSessionStarted(); +require_once __DIR__ . '/../bootstrap/mail.php'; require_once __DIR__ . '/../bootstrap/sentinel.php'; $app = new App(); +$app->bind('mailer', fn() => new Mailer()); $router = new Router(); require_once __DIR__ . '/../routes/web.php';