- Implemented the full Fat Bottom Grille storefront and staff dashboard.

Highlights include configurable menus, specials, cart/checkout, taxes, 
customer accounts with email 2FA, newsletters, analytics, refunds, 
PDF/CSV exports, Square/Mailgun adapters, and optional MySQL migration.

See README.md for setup and production configuration. Verified 
PHP/JavaScript syntax, responsive layouts, registration, 2FA, checkout, 
dashboard workflows, settings persistence, and PDF generation.
Real Square, Mailgun, and MySQL connections require credentials/server 
access. Square implementation follows the official Payments API and 
Refunds API.
This commit is contained in:
Ty Clifford
2026-06-10 13:27:42 -04:00
parent d46b0be488
commit 16235369cb
49 changed files with 7685 additions and 2 deletions
+80
View File
@@ -0,0 +1,80 @@
<?php
declare(strict_types=1);
namespace FatBottom\Services;
use FatBottom\Core\Config;
use RuntimeException;
final class Mailer
{
public function __construct(private readonly Config $config)
{
}
public function send(string $to, string $subject, string $html): bool
{
if (!(bool) $this->config->get('mailgun.enabled', false)) {
$this->writeToLog($to, $subject, $html);
return true;
}
if (!function_exists('curl_init')) {
throw new RuntimeException('The PHP cURL extension is required for Mailgun.');
}
$domain = (string) $this->config->get('mailgun.domain');
$apiKey = (string) $this->config->get('mailgun.api_key');
if ($domain === '' || $apiKey === '') {
throw new RuntimeException('Mailgun is enabled but its credentials are incomplete.');
}
$from = sprintf(
'%s <%s>',
$this->config->get('mailgun.from_name', 'Fat Bottom Grille'),
$this->config->get('mailgun.from_email')
);
$curl = curl_init('https://api.mailgun.net/v3/' . rawurlencode($domain) . '/messages');
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERPWD => 'api:' . $apiKey,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => [
'from' => $from,
'to' => $to,
'subject' => $subject,
'html' => $html,
],
CURLOPT_TIMEOUT => 20,
]);
$response = curl_exec($curl);
$status = (int) curl_getinfo($curl, CURLINFO_RESPONSE_CODE);
$error = curl_error($curl);
curl_close($curl);
if ($response === false || $status < 200 || $status >= 300) {
throw new RuntimeException('Mailgun delivery failed: ' . ($error ?: 'HTTP ' . $status));
}
return true;
}
private function writeToLog(string $to, string $subject, string $html): void
{
$directory = BASE_PATH . '/storage/logs';
if (!is_dir($directory)) {
mkdir($directory, 0775, true);
}
$line = sprintf(
"[%s] TO: %s | SUBJECT: %s\n%s\n\n",
date('c'),
$to,
$subject,
trim(strip_tags($html))
);
file_put_contents($directory . '/mail.log', $line, FILE_APPEND | LOCK_EX);
}
}