Generic framework features, code-only — the tables come from the schema snapshot:
- controllers/account.php — email signup (verified) + Google OAuth (server-side auth-code
flow, async-popup friendly); native password_hash sessions on sp_users; sp_oauth_accounts +
sp_user_tokens; PublicController origin+throttle guards; {ok,data,error} envelope.
- Helpers/Mailer.php — transactional email: Brevo HTTP API (Guzzle) -> SMTP fallback
(PHPMailer) -> logs; config in sp_settings (encrypted).
- cli/rebuild.php — queue a static rebuild (sp_settings rebuild_pending flag) after DB edits.
- cli/resources.php — read a curated source registry (mde_resources) per agent_type.
- db/seed_google_oauth.php, db/seed_email.php — env-seeded encrypted secrets into sp_settings.
- templates/emails/verify.html — verification email template.
- composer.json — declare guzzlehttp/guzzle ^7.10 (account.php + Mailer use GuzzleHttp\Client).
Depends on the schema-snapshot branch (sp_users / sp_oauth_accounts / sp_user_tokens /
sp_settings / mde_resources) plus `composer require guzzlehttp/guzzle`. No data.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01FMQeUnUrAeexcZ7P2Hxa6G
100 lines
4.3 KiB
PHP
100 lines
4.3 KiB
PHP
<?php
|
|
/**
|
|
* Transactional email sender.
|
|
*
|
|
* Config lives in sp_settings (keyval='email', group='email', encrypted=1):
|
|
* { "provider":"brevo", "api_key":<encrypted>, "from_email":"...", "from_name":"..." }
|
|
* Seed it with db/seed_email.php (reads BREVO_API_KEY from the environment).
|
|
*
|
|
* Send order: Brevo HTTP API (if configured) → SMTP via config.php EMAIL* constants
|
|
* (if set) → false. Callers treat false as "not sent" and log a fallback (e.g. the
|
|
* account verification link) so flows stay testable before email is wired up.
|
|
*/
|
|
class Mailer
|
|
{
|
|
/** @return bool true when the provider accepted the message. */
|
|
public static function send(string $toEmail, string $toName, string $subject, string $html, string $text = ''): bool
|
|
{
|
|
$cfg = json_decode((string) \Db::getValue(
|
|
"SELECT metval FROM sp_settings WHERE keyval = 'email' AND status = 1 LIMIT 1"
|
|
), true) ?: [];
|
|
|
|
if (strtolower((string) ($cfg['provider'] ?? '')) === 'brevo' && !empty($cfg['api_key'])) {
|
|
return self::sendBrevo($cfg, $toEmail, $toName, $subject, $html, $text);
|
|
}
|
|
|
|
$host = defined('EMAILHOST') ? (string) EMAILHOST : '';
|
|
if ($host !== '' && !str_starts_with($host, 'ADD')) {
|
|
return self::sendSmtp($toEmail, $toName, $subject, $html, $text);
|
|
}
|
|
|
|
return false; // unconfigured
|
|
}
|
|
|
|
private static function fromEmail(array $cfg): string
|
|
{
|
|
$from = trim((string) ($cfg['from_email'] ?? ''));
|
|
return $from !== '' ? $from : 'noreply@' . (parse_url(URL, PHP_URL_HOST) ?: 'localhost');
|
|
}
|
|
|
|
private static function fromName(array $cfg): string
|
|
{
|
|
$name = trim((string) ($cfg['from_name'] ?? ''));
|
|
return $name !== '' ? $name : (defined('PROJECT_NAME') ? PROJECT_NAME : 'Website');
|
|
}
|
|
|
|
private static function sendBrevo(array $cfg, string $toEmail, string $toName, string $subject, string $html, string $text): bool
|
|
{
|
|
try {
|
|
$http = new \GuzzleHttp\Client(['timeout' => 12, 'http_errors' => false]);
|
|
$res = $http->post('https://api.brevo.com/v3/smtp/email', [
|
|
'headers' => [
|
|
'api-key' => \Functions::Decrypt((string) $cfg['api_key']),
|
|
'accept' => 'application/json',
|
|
'content-type' => 'application/json',
|
|
],
|
|
'json' => [
|
|
'sender' => ['name' => self::fromName($cfg), 'email' => self::fromEmail($cfg)],
|
|
'to' => [['email' => $toEmail, 'name' => $toName !== '' ? $toName : $toEmail]],
|
|
'subject' => $subject,
|
|
'htmlContent' => $html,
|
|
'textContent' => $text !== '' ? $text : trim(strip_tags($html)),
|
|
],
|
|
]);
|
|
$code = $res->getStatusCode();
|
|
if ($code >= 200 && $code < 300) {
|
|
return true;
|
|
}
|
|
error_log('[mailer] brevo HTTP ' . $code . ': ' . (string) $res->getBody());
|
|
return false;
|
|
} catch (\Throwable $e) {
|
|
error_log('[mailer] brevo exception: ' . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
private static function sendSmtp(string $toEmail, string $toName, string $subject, string $html, string $text): bool
|
|
{
|
|
try {
|
|
$mail = new \PHPMailer\PHPMailer\PHPMailer(true);
|
|
$mail->isSMTP();
|
|
$mail->Host = EMAILHOST;
|
|
$mail->SMTPAuth = true;
|
|
$mail->Username = defined('EMAILUSER') ? EMAILUSER : '';
|
|
$mail->Password = defined('EMAILPASSWORD') ? EMAILPASSWORD : '';
|
|
$mail->SMTPSecure = 'tls';
|
|
$mail->Port = 587;
|
|
$mail->setFrom('noreply@' . (parse_url(URL, PHP_URL_HOST) ?: 'localhost'), defined('PROJECT_NAME') ? PROJECT_NAME : 'Website');
|
|
$mail->addAddress($toEmail, $toName !== '' ? $toName : $toEmail);
|
|
$mail->isHTML(true);
|
|
$mail->Subject = $subject;
|
|
$mail->Body = $html;
|
|
$mail->AltBody = $text !== '' ? $text : trim(strip_tags($html));
|
|
$mail->send();
|
|
return true;
|
|
} catch (\Throwable $e) {
|
|
error_log('[mailer] smtp send failed: ' . $e->getMessage());
|
|
return false;
|
|
}
|
|
}
|
|
}
|