Includes all work from phases 6-9+ and frontend polish rounds 1 & 2: - Login history with device trust, new device notifications, session management - Churn prevention: cancellation surveys, winback campaigns with email sequences - Financial reports: revenue, P&L, tax, aging, refund, subscription reports with PDF/CSV/JSON export - Configurable checkout: plan config groups/options, build-your-own VPS - Frontend polish: fix broken legal links, add SEO meta tags, favicon, font display=swap, Head titles on all 14 marketing pages, mobile responsive fixes, AuthLayout legal footer, remove false 24/7 claims, hide empty stats, correct uptime SLA to 99.9%, GameServers notify buttons linked to /contact, 301 redirects for /terms and /privacy - WHMCS migration scripts - Update legal page effective dates to March 16, 2026 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
80 lines
2.2 KiB
PHP
80 lines
2.2 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace WhmcsMigrate;
|
|
|
|
use Monolog\Formatter\LineFormatter;
|
|
use Monolog\Handler\StreamHandler;
|
|
use Monolog\Level;
|
|
use Monolog\Logger as MonologLogger;
|
|
|
|
final class Logger
|
|
{
|
|
private MonologLogger $logger;
|
|
|
|
public function __construct(string $logDir, string $level = 'info')
|
|
{
|
|
if (! is_dir($logDir)) {
|
|
mkdir($logDir, 0755, true);
|
|
}
|
|
|
|
$this->logger = new MonologLogger('whmcs-migrate');
|
|
|
|
$monologLevel = Level::fromName($level);
|
|
|
|
// File handler — detailed format
|
|
$fileFormatter = new LineFormatter(
|
|
format: "[%datetime%] %level_name%: %message% %context%\n",
|
|
dateFormat: 'Y-m-d H:i:s',
|
|
allowInlineLineBreaks: true,
|
|
ignoreEmptyContextAndExtra: true,
|
|
);
|
|
|
|
$fileHandler = new StreamHandler($logDir . '/migration.log', $monologLevel);
|
|
$fileHandler->setFormatter($fileFormatter);
|
|
$this->logger->pushHandler($fileHandler);
|
|
|
|
// Console handler — colorized
|
|
$consoleFormatter = new LineFormatter(
|
|
format: "%level_name%: %message% %context%\n",
|
|
dateFormat: null,
|
|
allowInlineLineBreaks: true,
|
|
ignoreEmptyContextAndExtra: true,
|
|
);
|
|
|
|
$consoleHandler = new StreamHandler('php://stdout', $monologLevel);
|
|
$consoleHandler->setFormatter($consoleFormatter);
|
|
$this->logger->pushHandler($consoleHandler);
|
|
}
|
|
|
|
public function info(string $message, array $context = []): void
|
|
{
|
|
$this->logger->info($message, $context);
|
|
}
|
|
|
|
public function warning(string $message, array $context = []): void
|
|
{
|
|
$this->logger->warning($message, $context);
|
|
}
|
|
|
|
public function error(string $message, array $context = []): void
|
|
{
|
|
$this->logger->error($message, $context);
|
|
}
|
|
|
|
public function debug(string $message, array $context = []): void
|
|
{
|
|
$this->logger->debug($message, $context);
|
|
}
|
|
|
|
/**
|
|
* Log a visual section separator.
|
|
*/
|
|
public function section(string $title): void
|
|
{
|
|
$separator = str_repeat('=', 60);
|
|
$this->info("\n{$separator}\n{$title}\n{$separator}");
|
|
}
|
|
}
|