Change ConfigModel to SettingsModel (#62)
Reviewed-on: https://gitea.subcultureofone.org/greg/tkr/pulls/62 Co-authored-by: Greg Sarjeant <greg@subcultureofone.org> Co-committed-by: Greg Sarjeant <greg@subcultureofone.org>
This commit is contained in:
parent
38c35f9bff
commit
3df38de9fb
@ -46,11 +46,11 @@ $db = $prerequisites->getDatabase();
|
|||||||
if (!(preg_match('/tkr-setup$/', $path))) {
|
if (!(preg_match('/tkr-setup$/', $path))) {
|
||||||
try {
|
try {
|
||||||
$user_count = (int) $db->query("SELECT COUNT(*) FROM user")->fetchColumn();
|
$user_count = (int) $db->query("SELECT COUNT(*) FROM user")->fetchColumn();
|
||||||
$config = (new ConfigModel($db))->get();
|
$settings = (new SettingsModel($db))->get();
|
||||||
|
|
||||||
$hasUser = $user_count > 0;
|
$hasUser = $user_count > 0;
|
||||||
$hasUrl = !empty($config->baseUrl) && !empty($config->basePath);
|
$hasUrl = !empty($settings->baseUrl) && !empty($settings->basePath);
|
||||||
|
|
||||||
if (!$hasUser || !$hasUrl) {
|
if (!$hasUser || !$hasUrl) {
|
||||||
// Redirect to setup with auto-detected URL
|
// Redirect to setup with auto-detected URL
|
||||||
$autodetected = Util::getAutodetectedUrl();
|
$autodetected = Util::getAutodetectedUrl();
|
||||||
@ -77,7 +77,7 @@ global $app;
|
|||||||
|
|
||||||
$app = [
|
$app = [
|
||||||
'db' => $db,
|
'db' => $db,
|
||||||
'config' => (new ConfigModel($db))->get(),
|
'settings' => (new SettingsModel($db))->get(),
|
||||||
'user' => (new UserModel($db))->get(),
|
'user' => (new UserModel($db))->get(),
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -86,8 +86,8 @@ Session::start();
|
|||||||
Session::generateCsrfToken();
|
Session::generateCsrfToken();
|
||||||
|
|
||||||
// Remove the base path from the URL
|
// Remove the base path from the URL
|
||||||
if (strpos($path, $app['config']->basePath) === 0) {
|
if (strpos($path, $app['settings']->basePath) === 0) {
|
||||||
$path = substr($path, strlen($app['config']->basePath));
|
$path = substr($path, strlen($app['settings']->basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
// strip the trailing slash from the resulting route
|
// strip the trailing slash from the resulting route
|
||||||
@ -105,7 +105,7 @@ if ($method === 'POST' && $path != 'setup') {
|
|||||||
if (!Session::isValid($_POST['csrf_token'])) {
|
if (!Session::isValid($_POST['csrf_token'])) {
|
||||||
// Invalid session - redirect to /login
|
// Invalid session - redirect to /login
|
||||||
Log::info('Attempt to POST with invalid session. Redirecting to login.');
|
Log::info('Attempt to POST with invalid session. Redirecting to login.');
|
||||||
header('Location: ' . Util::buildRelativeUrl($app['config']->basePath, 'login'));
|
header('Location: ' . Util::buildRelativeUrl($app['settings']->basePath, 'login'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -11,41 +11,41 @@ class AdminController extends Controller {
|
|||||||
|
|
||||||
public function showSetup(){
|
public function showSetup(){
|
||||||
$data = $this->getAdminData(true);
|
$data = $this->getAdminData(true);
|
||||||
|
|
||||||
// Auto-detect URL and pre-fill if not already configured
|
// Auto-detect URL and pre-fill if not already configured
|
||||||
if (empty($data['config']->baseUrl) || empty($data['config']->basePath)) {
|
if (empty($data['settings']->baseUrl) || empty($data['settings']->basePath)) {
|
||||||
$autodetected = Util::getAutodetectedUrl();
|
$autodetected = Util::getAutodetectedUrl();
|
||||||
$data['autodetectedUrl'] = $autodetected;
|
$data['autodetectedUrl'] = $autodetected;
|
||||||
|
|
||||||
// Pre-fill empty values with auto-detected ones
|
// Pre-fill empty values with auto-detected ones
|
||||||
if (empty($data['config']->baseUrl)) {
|
if (empty($data['settings']->baseUrl)) {
|
||||||
$data['config']->baseUrl = $autodetected['baseUrl'];
|
$data['settings']->baseUrl = $autodetected['baseUrl'];
|
||||||
}
|
}
|
||||||
if (empty($data['config']->basePath)) {
|
if (empty($data['settings']->basePath)) {
|
||||||
$data['config']->basePath = $autodetected['basePath'];
|
$data['settings']->basePath = $autodetected['basePath'];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->render("admin.php", $data);
|
$this->render("admin.php", $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getAdminData(bool $isSetup): array {
|
public function getAdminData(bool $isSetup): array {
|
||||||
global $app;
|
global $app;
|
||||||
|
|
||||||
Log::debug("Loading admin page" . ($isSetup ? " (setup mode)" : ""));
|
Log::debug("Loading admin page" . ($isSetup ? " (setup mode)" : ""));
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'user' => $app['user'],
|
'user' => $app['user'],
|
||||||
'config' => $app['config'],
|
'settings' => $app['settings'],
|
||||||
'isSetup' => $isSetup,
|
'isSetup' => $isSetup,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleSave(){
|
public function handleSave(){
|
||||||
global $app;
|
global $app;
|
||||||
|
|
||||||
if (!Session::isLoggedIn()){
|
if (!Session::isLoggedIn()){
|
||||||
header('Location: ' . Util::buildRelativeUrl($app['config']->basePath, 'login'));
|
header('Location: ' . Util::buildRelativeUrl($app['settings']->basePath, 'login'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -64,9 +64,9 @@ class AdminController extends Controller {
|
|||||||
|
|
||||||
public function saveSettings(array $postData, bool $isSetup): array {
|
public function saveSettings(array $postData, bool $isSetup): array {
|
||||||
global $app;
|
global $app;
|
||||||
|
|
||||||
$result = ['success' => false, 'errors' => []];
|
$result = ['success' => false, 'errors' => []];
|
||||||
|
|
||||||
Log::debug("Processing settings save" . ($isSetup ? " (setup mode)" : ""));
|
Log::debug("Processing settings save" . ($isSetup ? " (setup mode)" : ""));
|
||||||
|
|
||||||
// handle form submission
|
// handle form submission
|
||||||
@ -95,7 +95,7 @@ class AdminController extends Controller {
|
|||||||
// Password
|
// Password
|
||||||
$password = $postData['password'] ?? '';
|
$password = $postData['password'] ?? '';
|
||||||
$confirmPassword = $postData['confirm_password'] ?? '';
|
$confirmPassword = $postData['confirm_password'] ?? '';
|
||||||
|
|
||||||
Log::info("Processing settings for user: $username");
|
Log::info("Processing settings for user: $username");
|
||||||
|
|
||||||
// Validate user profile
|
// Validate user profile
|
||||||
@ -145,16 +145,16 @@ class AdminController extends Controller {
|
|||||||
if (empty($errors)) {
|
if (empty($errors)) {
|
||||||
try {
|
try {
|
||||||
// Update site settings
|
// Update site settings
|
||||||
$app['config']->siteTitle = $siteTitle;
|
$app['settings']->siteTitle = $siteTitle;
|
||||||
$app['config']->siteDescription = $siteDescription;
|
$app['settings']->siteDescription = $siteDescription;
|
||||||
$app['config']->baseUrl = $baseUrl;
|
$app['settings']->baseUrl = $baseUrl;
|
||||||
$app['config']->basePath = $basePath;
|
$app['settings']->basePath = $basePath;
|
||||||
$app['config']->itemsPerPage = $itemsPerPage;
|
$app['settings']->itemsPerPage = $itemsPerPage;
|
||||||
$app['config']->strictAccessibility = $strictAccessibility;
|
$app['settings']->strictAccessibility = $strictAccessibility;
|
||||||
$app['config']->logLevel = $logLevel;
|
$app['settings']->logLevel = $logLevel;
|
||||||
|
|
||||||
// Save site settings and reload config from database
|
// Save site settings and reload config from database
|
||||||
$app['config'] = $app['config']->save();
|
$app['settings'] = $app['settings']->save();
|
||||||
Log::info("Site settings updated");
|
Log::info("Site settings updated");
|
||||||
|
|
||||||
// Update user profile
|
// Update user profile
|
||||||
@ -174,7 +174,7 @@ class AdminController extends Controller {
|
|||||||
|
|
||||||
Session::setFlashMessage('success', 'Settings updated');
|
Session::setFlashMessage('success', 'Settings updated');
|
||||||
$result['success'] = true;
|
$result['success'] = true;
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
Log::error("Failed to save settings: " . $e->getMessage());
|
Log::error("Failed to save settings: " . $e->getMessage());
|
||||||
Session::setFlashMessage('error', 'Failed to save settings');
|
Session::setFlashMessage('error', 'Failed to save settings');
|
||||||
@ -186,7 +186,7 @@ class AdminController extends Controller {
|
|||||||
}
|
}
|
||||||
$result['errors'] = $errors;
|
$result['errors'] = $errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,11 +4,11 @@ declare(strict_types=1);
|
|||||||
class AuthController extends Controller {
|
class AuthController extends Controller {
|
||||||
function showLogin(?string $error = null){
|
function showLogin(?string $error = null){
|
||||||
global $app;
|
global $app;
|
||||||
|
|
||||||
$csrf_token = Session::getCsrfToken();
|
$csrf_token = Session::getCsrfToken();
|
||||||
|
|
||||||
$vars = [
|
$vars = [
|
||||||
'config' => $app['config'],
|
'settings' => $app['settings'],
|
||||||
'csrf_token' => $csrf_token,
|
'csrf_token' => $csrf_token,
|
||||||
'error' => $error,
|
'error' => $error,
|
||||||
];
|
];
|
||||||
@ -34,7 +34,7 @@ class AuthController extends Controller {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
Session::newLoginSession($user);
|
Session::newLoginSession($user);
|
||||||
header('Location: ' . Util::buildRelativeUrl($app['config']->basePath));
|
header('Location: ' . Util::buildRelativeUrl($app['settings']->basePath));
|
||||||
exit;
|
exit;
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
Log::error("Failed to create login session for {$username}: " . $e->getMessage());
|
Log::error("Failed to create login session for {$username}: " . $e->getMessage());
|
||||||
@ -61,11 +61,11 @@ class AuthController extends Controller {
|
|||||||
|
|
||||||
function handleLogout(){
|
function handleLogout(){
|
||||||
global $app;
|
global $app;
|
||||||
|
|
||||||
Log::info("Logout from user " . $_SESSION['username']);
|
Log::info("Logout from user " . $_SESSION['username']);
|
||||||
Session::end();
|
Session::end();
|
||||||
|
|
||||||
header('Location: ' . Util::buildRelativeUrl($app['config']->basePath));
|
header('Location: ' . Util::buildRelativeUrl($app['settings']->basePath));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -17,9 +17,9 @@ class Controller {
|
|||||||
|
|
||||||
// Add custom CSS filename if needed
|
// Add custom CSS filename if needed
|
||||||
global $app;
|
global $app;
|
||||||
if ($app['config']->cssId) {
|
if ($app['settings']->cssId) {
|
||||||
$cssModel = new CssModel($app['db']);
|
$cssModel = new CssModel($app['db']);
|
||||||
$cssFile = $cssModel->getById($app['config']->cssId);
|
$cssFile = $cssModel->getById($app['settings']->cssId);
|
||||||
$vars['customCssFilename'] = $cssFile['filename'] ?? null;
|
$vars['customCssFilename'] = $cssFile['filename'] ?? null;
|
||||||
} else {
|
} else {
|
||||||
$vars['customCssFilename'] = null;
|
$vars['customCssFilename'] = null;
|
||||||
|
@ -9,7 +9,7 @@ class CssController extends Controller {
|
|||||||
|
|
||||||
$vars = [
|
$vars = [
|
||||||
'user' => $app['user'],
|
'user' => $app['user'],
|
||||||
'config' => $app['config'],
|
'settings' => $app['settings'],
|
||||||
'customCss' => $customCss,
|
'customCss' => $customCss,
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -114,8 +114,8 @@ class CssController extends Controller {
|
|||||||
|
|
||||||
// Set the theme back to default
|
// Set the theme back to default
|
||||||
try {
|
try {
|
||||||
$app['config']->cssId = null;
|
$app['settings']->cssId = null;
|
||||||
$app['config'] = $app['config']->save();
|
$app['settings'] = $app['settings']->save();
|
||||||
Session::setFlashMessage('success', 'Theme ' . $cssFilename . ' deleted.');
|
Session::setFlashMessage('success', 'Theme ' . $cssFilename . ' deleted.');
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
Log::error("Failed to update config after deleting theme: " . $e->getMessage());
|
Log::error("Failed to update config after deleting theme: " . $e->getMessage());
|
||||||
@ -129,14 +129,14 @@ class CssController extends Controller {
|
|||||||
try {
|
try {
|
||||||
if ($_POST['selectCssFile']){
|
if ($_POST['selectCssFile']){
|
||||||
// Set custom theme
|
// Set custom theme
|
||||||
$app['config']->cssId = $_POST['selectCssFile'];
|
$app['settings']->cssId = $_POST['selectCssFile'];
|
||||||
} else {
|
} else {
|
||||||
// Set default theme
|
// Set default theme
|
||||||
$app['config']->cssId = null;
|
$app['settings']->cssId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the site theme
|
// Update the site theme
|
||||||
$app['config'] = $app['config']->save();
|
$app['settings'] = $app['settings']->save();
|
||||||
Session::setFlashMessage('success', 'Theme applied.');
|
Session::setFlashMessage('success', 'Theme applied.');
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
Log::error("Failed to save theme setting: " . $e->getMessage());
|
Log::error("Failed to save theme setting: " . $e->getMessage());
|
||||||
|
@ -16,7 +16,7 @@ declare(strict_types=1);
|
|||||||
}
|
}
|
||||||
|
|
||||||
$vars = [
|
$vars = [
|
||||||
'config' => $app['config'],
|
'settings' => $app['settings'],
|
||||||
'emojiList' => $emojiList,
|
'emojiList' => $emojiList,
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ declare(strict_types=1);
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
header('Location: ' . Util::buildRelativeUrl($app['config']->basePath, 'admin/emoji'));
|
header('Location: ' . Util::buildRelativeUrl($app['settings']->basePath, 'admin/emoji'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,10 +6,10 @@ class FeedController extends Controller {
|
|||||||
|
|
||||||
public function __construct() {
|
public function __construct() {
|
||||||
global $app;
|
global $app;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$tickModel = new TickModel($app['db'], $app['config']);
|
$tickModel = new TickModel($app['db'], $app['settings']);
|
||||||
$this->ticks = $tickModel->getPage($app['config']->itemsPerPage);
|
$this->ticks = $tickModel->getPage($app['settings']->itemsPerPage);
|
||||||
Log::debug("Loaded " . count($this->ticks) . " ticks for feeds");
|
Log::debug("Loaded " . count($this->ticks) . " ticks for feeds");
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
Log::error("Failed to load ticks for feed: " . $e->getMessage());
|
Log::error("Failed to load ticks for feed: " . $e->getMessage());
|
||||||
@ -20,8 +20,8 @@ class FeedController extends Controller {
|
|||||||
|
|
||||||
public function rss(){
|
public function rss(){
|
||||||
global $app;
|
global $app;
|
||||||
|
|
||||||
$generator = new RssGenerator($app['config'], $this->ticks);
|
$generator = new RssGenerator($app['settings'], $this->ticks);
|
||||||
Log::debug("Generating RSS feed with " . count($this->ticks) . " ticks");
|
Log::debug("Generating RSS feed with " . count($this->ticks) . " ticks");
|
||||||
|
|
||||||
header('Content-Type: ' . $generator->getContentType());
|
header('Content-Type: ' . $generator->getContentType());
|
||||||
@ -30,8 +30,8 @@ class FeedController extends Controller {
|
|||||||
|
|
||||||
public function atom(){
|
public function atom(){
|
||||||
global $app;
|
global $app;
|
||||||
|
|
||||||
$generator = new AtomGenerator($app['config'], $this->ticks);
|
$generator = new AtomGenerator($app['settings'], $this->ticks);
|
||||||
Log::debug("Generating Atom feed with " . count($this->ticks) . " ticks");
|
Log::debug("Generating Atom feed with " . count($this->ticks) . " ticks");
|
||||||
|
|
||||||
header('Content-Type: ' . $generator->getContentType());
|
header('Content-Type: ' . $generator->getContentType());
|
||||||
|
@ -9,24 +9,24 @@ class HomeController extends Controller {
|
|||||||
$data = $this->getHomeData($page);
|
$data = $this->getHomeData($page);
|
||||||
$this->render("home.php", $data);
|
$this->render("home.php", $data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHomeData(int $page): array {
|
public function getHomeData(int $page): array {
|
||||||
global $app;
|
global $app;
|
||||||
|
|
||||||
Log::debug("Loading home page $page");
|
Log::debug("Loading home page $page");
|
||||||
|
|
||||||
$tickModel = new TickModel($app['db'], $app['config']);
|
$tickModel = new TickModel($app['db'], $app['settings']);
|
||||||
$limit = $app['config']->itemsPerPage;
|
$limit = $app['settings']->itemsPerPage;
|
||||||
$offset = ($page - 1) * $limit;
|
$offset = ($page - 1) * $limit;
|
||||||
$ticks = $tickModel->getPage($limit, $offset);
|
$ticks = $tickModel->getPage($limit, $offset);
|
||||||
|
|
||||||
$view = new TicksView($app['config'], $ticks, $page);
|
$view = new TicksView($app['settings'], $ticks, $page);
|
||||||
$tickList = $view->getHtml();
|
$tickList = $view->getHtml();
|
||||||
|
|
||||||
Log::info("Home page loaded with " . count($ticks) . " ticks");
|
Log::info("Home page loaded with " . count($ticks) . " ticks");
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'config' => $app['config'],
|
'settings' => $app['settings'],
|
||||||
'user' => $app['user'],
|
'user' => $app['user'],
|
||||||
'tickList' => $tickList,
|
'tickList' => $tickList,
|
||||||
];
|
];
|
||||||
@ -36,34 +36,34 @@ class HomeController extends Controller {
|
|||||||
// Saves the tick and reloads the homepage
|
// Saves the tick and reloads the homepage
|
||||||
public function handleTick(){
|
public function handleTick(){
|
||||||
global $app;
|
global $app;
|
||||||
|
|
||||||
$result = $this->processTick($_POST);
|
$result = $this->processTick($_POST);
|
||||||
|
|
||||||
// redirect to the index (will show the latest tick if one was sent)
|
// redirect to the index (will show the latest tick if one was sent)
|
||||||
header('Location: ' . Util::buildRelativeUrl($app['config']->basePath));
|
header('Location: ' . Util::buildRelativeUrl($app['settings']->basePath));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function processTick(array $postData): array {
|
public function processTick(array $postData): array {
|
||||||
global $app;
|
global $app;
|
||||||
|
|
||||||
$result = ['success' => false, 'message' => ''];
|
$result = ['success' => false, 'message' => ''];
|
||||||
|
|
||||||
if (!isset($postData['new_tick'])) {
|
if (!isset($postData['new_tick'])) {
|
||||||
Log::warning("Tick submission without new_tick field");
|
Log::warning("Tick submission without new_tick field");
|
||||||
$result['message'] = 'No tick content provided';
|
$result['message'] = 'No tick content provided';
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
$tickContent = trim($postData['new_tick']);
|
$tickContent = trim($postData['new_tick']);
|
||||||
if (empty($tickContent)) {
|
if (empty($tickContent)) {
|
||||||
Log::debug("Empty tick submission ignored");
|
Log::debug("Empty tick submission ignored");
|
||||||
$result['message'] = 'Empty tick ignored';
|
$result['message'] = 'Empty tick ignored';
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$tickModel = new TickModel($app['db'], $app['config']);
|
$tickModel = new TickModel($app['db'], $app['settings']);
|
||||||
$tickModel->insert($tickContent);
|
$tickModel->insert($tickContent);
|
||||||
Log::info("New tick created: " . substr($tickContent, 0, 50) . (strlen($tickContent) > 50 ? '...' : ''));
|
Log::info("New tick created: " . substr($tickContent, 0, 50) . (strlen($tickContent) > 50 ? '...' : ''));
|
||||||
$result['success'] = true;
|
$result['success'] = true;
|
||||||
@ -72,7 +72,7 @@ class HomeController extends Controller {
|
|||||||
Log::error("Failed to save tick: " . $e->getMessage());
|
Log::error("Failed to save tick: " . $e->getMessage());
|
||||||
$result['message'] = 'Failed to save tick';
|
$result['message'] = 'Failed to save tick';
|
||||||
}
|
}
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -10,10 +10,10 @@ class LogController extends Controller {
|
|||||||
|
|
||||||
public function index() {
|
public function index() {
|
||||||
global $app;
|
global $app;
|
||||||
|
|
||||||
// Ensure user is logged in
|
// Ensure user is logged in
|
||||||
if (!Session::isLoggedIn()) {
|
if (!Session::isLoggedIn()) {
|
||||||
header('Location: ' . Util::buildRelativeUrl($app['config']->basePath, 'login'));
|
header('Location: ' . Util::buildRelativeUrl($app['settings']->basePath, 'login'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -49,7 +49,7 @@ class LogController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'config' => $app['config'],
|
'settings' => $app['settings'],
|
||||||
'logEntries' => $logEntries,
|
'logEntries' => $logEntries,
|
||||||
'availableRoutes' => $availableRoutes,
|
'availableRoutes' => $availableRoutes,
|
||||||
'availableLevels' => $availableLevels,
|
'availableLevels' => $availableLevels,
|
||||||
@ -80,7 +80,7 @@ class LogController extends Controller {
|
|||||||
Log::warning("Failed to read log file: $file");
|
Log::warning("Failed to read log file: $file");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (array_reverse($lines) as $line) {
|
foreach (array_reverse($lines) as $line) {
|
||||||
if (count($entries) >= $limit) break 2;
|
if (count($entries) >= $limit) break 2;
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ declare(strict_types=1);
|
|||||||
$moodPicker = $view->renderMoodPicker(self::getEmojisWithLabels(), $app['user']->mood);
|
$moodPicker = $view->renderMoodPicker(self::getEmojisWithLabels(), $app['user']->mood);
|
||||||
|
|
||||||
$vars = [
|
$vars = [
|
||||||
'config' => $app['config'],
|
'settings' => $app['settings'],
|
||||||
'moodPicker' => $moodPicker,
|
'moodPicker' => $moodPicker,
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -41,7 +41,7 @@ declare(strict_types=1);
|
|||||||
}
|
}
|
||||||
|
|
||||||
// go back to the index and show the updated mood
|
// go back to the index and show the updated mood
|
||||||
header('Location: ' . Util::buildRelativeUrl($app['config']->basePath));
|
header('Location: ' . Util::buildRelativeUrl($app['settings']->basePath));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,23 +4,23 @@ declare(strict_types=1);
|
|||||||
class TickController extends Controller{
|
class TickController extends Controller{
|
||||||
public function index(int $id){
|
public function index(int $id){
|
||||||
global $app;
|
global $app;
|
||||||
|
|
||||||
Log::debug("Fetching tick with ID: {$id}");
|
Log::debug("Fetching tick with ID: {$id}");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$tickModel = new TickModel($app['db'], $app['config']);
|
$tickModel = new TickModel($app['db'], $app['settings']);
|
||||||
$vars = $tickModel->get($id);
|
$vars = $tickModel->get($id);
|
||||||
|
|
||||||
if (empty($vars) || !isset($vars['tick'])) {
|
if (empty($vars) || !isset($vars['tick'])) {
|
||||||
Log::warning("Tick not found for ID: {$id}");
|
Log::warning("Tick not found for ID: {$id}");
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
echo '<h1>404 - Tick Not Found</h1>';
|
echo '<h1>404 - Tick Not Found</h1>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Log::info("Successfully loaded tick {$id}: " . substr($vars['tick'], 0, 50) . (strlen($vars['tick']) > 50 ? '...' : ''));
|
Log::info("Successfully loaded tick {$id}: " . substr($vars['tick'], 0, 50) . (strlen($vars['tick']) > 50 ? '...' : ''));
|
||||||
$this->render('tick.php', $vars);
|
$this->render('tick.php', $vars);
|
||||||
|
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
Log::error("Failed to load tick {$id}: " . $e->getMessage());
|
Log::error("Failed to load tick {$id}: " . $e->getMessage());
|
||||||
http_response_code(500);
|
http_response_code(500);
|
||||||
|
@ -15,10 +15,10 @@ class AtomGenerator extends FeedGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function buildFeed(): string {
|
private function buildFeed(): string {
|
||||||
Log::debug("Building Atom feed for " . $this->config->siteTitle);
|
Log::debug("Building Atom feed for " . $this->settings->siteTitle);
|
||||||
$feedTitle = Util::escape_xml($this->config->siteTitle . " Atom Feed");
|
$feedTitle = Util::escape_xml($this->settings->siteTitle . " Atom Feed");
|
||||||
$siteUrl = Util::escape_xml(Util::buildUrl($this->config->baseUrl, $this->config->basePath));
|
$siteUrl = Util::escape_xml(Util::buildUrl($this->settings->baseUrl, $this->settings->basePath));
|
||||||
$feedUrl = Util::escape_xml(Util::buildUrl($this->config->baseUrl, $this->config->basePath, 'feed/atom'));
|
$feedUrl = Util::escape_xml(Util::buildUrl($this->settings->baseUrl, $this->settings->basePath, 'feed/atom'));
|
||||||
$updated = date(DATE_ATOM, strtotime($this->ticks[0]['timestamp'] ?? 'now'));
|
$updated = date(DATE_ATOM, strtotime($this->ticks[0]['timestamp'] ?? 'now'));
|
||||||
|
|
||||||
ob_start();
|
ob_start();
|
||||||
@ -33,7 +33,7 @@ class AtomGenerator extends FeedGenerator {
|
|||||||
<updated><?php echo $updated ?></updated>
|
<updated><?php echo $updated ?></updated>
|
||||||
<id><?php echo $siteUrl ?></id>
|
<id><?php echo $siteUrl ?></id>
|
||||||
<author>
|
<author>
|
||||||
<name><?= Util::escape_xml($this->config->siteTitle) ?></name>
|
<name><?= Util::escape_xml($this->settings->siteTitle) ?></name>
|
||||||
</author>
|
</author>
|
||||||
<?php foreach ($this->ticks as $tick):
|
<?php foreach ($this->ticks as $tick):
|
||||||
// build the tick entry components
|
// build the tick entry components
|
||||||
|
@ -5,11 +5,11 @@ declare(strict_types=1);
|
|||||||
// Specific feeds (RSS, Atom, etc.) will inherit from this.
|
// Specific feeds (RSS, Atom, etc.) will inherit from this.
|
||||||
// This will wrap the basic generator functionality.
|
// This will wrap the basic generator functionality.
|
||||||
abstract class FeedGenerator {
|
abstract class FeedGenerator {
|
||||||
protected $config;
|
protected $settings;
|
||||||
protected $ticks;
|
protected $ticks;
|
||||||
|
|
||||||
public function __construct(ConfigModel $config, array $ticks) {
|
public function __construct(SettingsModel $settings, array $ticks) {
|
||||||
$this->config = $config;
|
$this->settings = $settings;
|
||||||
$this->ticks = $ticks;
|
$this->ticks = $ticks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -17,10 +17,10 @@ abstract class FeedGenerator {
|
|||||||
abstract public function getContentType(): string;
|
abstract public function getContentType(): string;
|
||||||
|
|
||||||
protected function buildTickUrl(int $tickId): string {
|
protected function buildTickUrl(int $tickId): string {
|
||||||
return Util::buildUrl($this->config->baseUrl, $this->config->basePath, "tick/{$tickId}");
|
return Util::buildUrl($this->settings->baseUrl, $this->settings->basePath, "tick/{$tickId}");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function getSiteUrl(): string {
|
protected function getSiteUrl(): string {
|
||||||
return Util::buildUrl($this->config->baseUrl, $this->config->basePath);
|
return Util::buildUrl($this->settings->baseUrl, $this->settings->basePath);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -17,16 +17,16 @@ class RssGenerator extends FeedGenerator {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private function buildChannel(): string {
|
private function buildChannel(): string {
|
||||||
Log::debug("Building RSS channel for " . $this->config->siteTitle);
|
Log::debug("Building RSS channel for " . $this->settings->siteTitle);
|
||||||
ob_start();
|
ob_start();
|
||||||
?>
|
?>
|
||||||
<channel>
|
<channel>
|
||||||
<title><?php echo Util::escape_xml($this->config->siteTitle . ' RSS Feed') ?></title>
|
<title><?php echo Util::escape_xml($this->settings->siteTitle . ' RSS Feed') ?></title>
|
||||||
<link><?php echo Util::escape_xml(Util::buildUrl($this->config->baseUrl, $this->config->basePath))?></link>
|
<link><?php echo Util::escape_xml(Util::buildUrl($this->settings->baseUrl, $this->settings->basePath))?></link>
|
||||||
<atom:link href="<?php echo Util::escape_xml(Util::buildUrl($this->config->baseUrl, $this->config->basePath, 'feed/rss'))?>"
|
<atom:link href="<?php echo Util::escape_xml(Util::buildUrl($this->settings->baseUrl, $this->settings->basePath, 'feed/rss'))?>"
|
||||||
rel="self"
|
rel="self"
|
||||||
type="application/rss+xml" />
|
type="application/rss+xml" />
|
||||||
<description><?php echo Util::escape_xml($this->config->siteDescription) ?></description>
|
<description><?php echo Util::escape_xml($this->settings->siteDescription) ?></description>
|
||||||
<language>en-us</language>
|
<language>en-us</language>
|
||||||
<lastBuildDate><?php echo date(DATE_RSS); ?></lastBuildDate>
|
<lastBuildDate><?php echo date(DATE_RSS); ?></lastBuildDate>
|
||||||
<?php foreach ($this->ticks as $tick):
|
<?php foreach ($this->ticks as $tick):
|
||||||
|
@ -52,7 +52,7 @@ class Log {
|
|||||||
|
|
||||||
private static function write($level, $message) {
|
private static function write($level, $message) {
|
||||||
global $app;
|
global $app;
|
||||||
$logLevel = $app['config']->logLevel ?? self::LEVELS['INFO'];
|
$logLevel = $app['settings']->logLevel ?? self::LEVELS['INFO'];
|
||||||
|
|
||||||
// Only log messages if they're at or above the configured log level.
|
// Only log messages if they're at or above the configured log level.
|
||||||
if (self::LEVELS[$level] < $logLevel){
|
if (self::LEVELS[$level] < $logLevel){
|
||||||
|
@ -30,7 +30,7 @@ class Util {
|
|||||||
global $app;
|
global $app;
|
||||||
$escaped_url = rtrim($matches[1], '.,!?;:)]}>');
|
$escaped_url = rtrim($matches[1], '.,!?;:)]}>');
|
||||||
$clean_url = html_entity_decode($escaped_url, ENT_QUOTES, 'UTF-8');
|
$clean_url = html_entity_decode($escaped_url, ENT_QUOTES, 'UTF-8');
|
||||||
$tabIndex = $app['config']->strictAccessibility ? ' tabindex="0"' : '';
|
$tabIndex = $app['settings']->strictAccessibility ? ' tabindex="0"' : '';
|
||||||
|
|
||||||
return '<a' . $tabIndex . ' href="' . $clean_url . '"' . $link_attrs . '>' . $escaped_url . '</a>';
|
return '<a' . $tabIndex . ' href="' . $clean_url . '"' . $link_attrs . '>' . $escaped_url . '</a>';
|
||||||
},
|
},
|
||||||
@ -114,29 +114,29 @@ class Util {
|
|||||||
// Detect base URL
|
// Detect base URL
|
||||||
$baseUrl = ($_SERVER['HTTPS'] ?? 'off') === 'on' ? 'https://' : 'http://';
|
$baseUrl = ($_SERVER['HTTPS'] ?? 'off') === 'on' ? 'https://' : 'http://';
|
||||||
$baseUrl .= $_SERVER['HTTP_HOST'] ?? 'localhost';
|
$baseUrl .= $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||||||
|
|
||||||
// Don't include standard ports in URL
|
// Don't include standard ports in URL
|
||||||
$port = $_SERVER['SERVER_PORT'] ?? null;
|
$port = $_SERVER['SERVER_PORT'] ?? null;
|
||||||
if ($port && $port != 80 && $port != 443) {
|
if ($port && $port != 80 && $port != 443) {
|
||||||
$baseUrl .= ':' . $port;
|
$baseUrl .= ':' . $port;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Detect base path from script location
|
// Detect base path from script location
|
||||||
$scriptName = $_SERVER['SCRIPT_NAME'] ?? '/index.php';
|
$scriptName = $_SERVER['SCRIPT_NAME'] ?? '/index.php';
|
||||||
$basePath = dirname($scriptName);
|
$basePath = dirname($scriptName);
|
||||||
|
|
||||||
if ($basePath === '/' || $basePath === '.' || $basePath === '') {
|
if ($basePath === '/' || $basePath === '.' || $basePath === '') {
|
||||||
$basePath = '/';
|
$basePath = '/';
|
||||||
} else {
|
} else {
|
||||||
$basePath = '/' . trim($basePath, '/') . '/';
|
$basePath = '/' . trim($basePath, '/') . '/';
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct full URL
|
// Construct full URL
|
||||||
$fullUrl = $baseUrl;
|
$fullUrl = $baseUrl;
|
||||||
if ($basePath !== '/') {
|
if ($basePath !== '/') {
|
||||||
$fullUrl .= ltrim($basePath, '/');
|
$fullUrl .= ltrim($basePath, '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'baseUrl' => $baseUrl,
|
'baseUrl' => $baseUrl,
|
||||||
'basePath' => $basePath,
|
'basePath' => $basePath,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
class ConfigModel {
|
class SettingsModel {
|
||||||
// properties and default values
|
// properties and default values
|
||||||
public string $siteTitle = 'My tkr';
|
public string $siteTitle = 'My tkr';
|
||||||
public string $siteDescription = '';
|
public string $siteDescription = '';
|
@ -2,8 +2,8 @@
|
|||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
class TickModel {
|
class TickModel {
|
||||||
public function __construct(private PDO $db, private ConfigModel $config) {}
|
public function __construct(private PDO $db, private SettingsModel $settings) {}
|
||||||
|
|
||||||
public function getPage(int $limit, int $offset = 0): array {
|
public function getPage(int $limit, int $offset = 0): array {
|
||||||
$stmt = $this->db->prepare("SELECT id, timestamp, tick FROM tick ORDER BY timestamp DESC LIMIT ? OFFSET ?");
|
$stmt = $this->db->prepare("SELECT id, timestamp, tick FROM tick ORDER BY timestamp DESC LIMIT ? OFFSET ?");
|
||||||
$stmt->execute([$limit, $offset]);
|
$stmt->execute([$limit, $offset]);
|
||||||
@ -32,7 +32,7 @@ class TickModel {
|
|||||||
return [
|
return [
|
||||||
'tickTime' => $row['timestamp'],
|
'tickTime' => $row['timestamp'],
|
||||||
'tick' => $row['tick'],
|
'tick' => $row['tick'],
|
||||||
'config' => $this->config,
|
'settings' => $this->settings,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,15 +4,15 @@ declare(strict_types=1);
|
|||||||
class TicksView {
|
class TicksView {
|
||||||
private $html;
|
private $html;
|
||||||
|
|
||||||
public function __construct(ConfigModel $config, array $ticks, int $page){
|
public function __construct(SettingsModel $settings, array $ticks, int $page){
|
||||||
$this->html = $this->render($config, $ticks, $page);
|
$this->html = $this->render($settings, $ticks, $page);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getHtml(): string {
|
public function getHtml(): string {
|
||||||
return $this->html;
|
return $this->html;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function render(ConfigModel $config, array $ticks, int $page): string{
|
private function render(SettingsModel $settings, array $ticks, int $page): string{
|
||||||
ob_start();
|
ob_start();
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@ -23,18 +23,18 @@ class TicksView {
|
|||||||
$relativeTime = Util::relative_time($tick['timestamp']);
|
$relativeTime = Util::relative_time($tick['timestamp']);
|
||||||
?>
|
?>
|
||||||
<li class="tick" tabindex="0">
|
<li class="tick" tabindex="0">
|
||||||
<time datetime="<?php echo $datetime->format('c') ?>"><?php echo Util::escape_html($relativeTime) ?></time>
|
🗑️ <time datetime="<?php echo $datetime->format('c') ?>"><?php echo Util::escape_html($relativeTime) ?></time>
|
||||||
<span class="tick-text"><?php echo Util::linkify(Util::escape_html($tick['tick'])) ?></span>
|
<span class="tick-text"><?php echo Util::linkify(Util::escape_html($tick['tick'])) ?></span>
|
||||||
</li>
|
</li>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="tick-pagination">
|
<div class="tick-pagination">
|
||||||
<?php if ($page > 1): ?>
|
<?php if ($page > 1): ?>
|
||||||
<a <?php if($config->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
<a <?php if($settings->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
||||||
href="?page=<?php echo $page - 1 ?>">« Newer</a>
|
href="?page=<?php echo $page - 1 ?>">« Newer</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<?php if (count($ticks) === $config->itemsPerPage): ?>
|
<?php if (count($ticks) === $settings->itemsPerPage): ?>
|
||||||
<a <?php if($config->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
<a <?php if($settings->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
||||||
href="?page=<?php echo $page + 1 ?>">Older »</a>
|
href="?page=<?php echo $page + 1 ?>">Older »</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php /** @var bool $isLoggedIn */ ?>
|
<?php /** @var bool $isLoggedIn */ ?>
|
||||||
<?php /** @var ConfigModel $config */ ?>
|
<?php /** @var SettingsModel $settings */ ?>
|
||||||
<?php /** @var UserModel $user */ ?>
|
<?php /** @var UserModel $user */ ?>
|
||||||
<?php /** @var string $childTemplateFile */ ?>
|
<?php /** @var string $childTemplateFile */ ?>
|
||||||
<?php /** @var string $customCssFilename */ ?>
|
<?php /** @var string $customCssFilename */ ?>
|
||||||
@ -7,23 +7,23 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
<head>
|
<head>
|
||||||
<title><?= $config->siteTitle ?></title>
|
<title><?= $settings->siteTitle ?></title>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<link rel="stylesheet"
|
<link rel="stylesheet"
|
||||||
href="<?= Util::escape_html(Util::buildRelativeUrl($config->basePath, 'css/default.css')) ?>">
|
href="<?= Util::escape_html(Util::buildRelativeUrl($settings->basePath, 'css/default.css')) ?>">
|
||||||
<?php if (!empty($config->cssId)): ?>
|
<?php if (!empty($settings->cssId)): ?>
|
||||||
<link rel="stylesheet"
|
<link rel="stylesheet"
|
||||||
href="<?= Util::escape_html(Util::buildRelativeUrl($config->basePath, 'css/custom/' . $customCssFilename)) ?>">
|
href="<?= Util::escape_html(Util::buildRelativeUrl($settings->basePath, 'css/custom/' . $customCssFilename)) ?>">
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<link rel="alternate"
|
<link rel="alternate"
|
||||||
type="application/rss+xml"
|
type="application/rss+xml"
|
||||||
title="<?php echo Util::escape_html($config->siteTitle) ?> RSS Feed"
|
title="<?php echo Util::escape_html($settings->siteTitle) ?> RSS Feed"
|
||||||
href="<?php echo Util::escape_html($config->baseUrl . $config->basePath)?>feed/rss/">
|
href="<?php echo Util::escape_html($settings->baseUrl . $settings->basePath)?>feed/rss/">
|
||||||
<link rel="alternate"
|
<link rel="alternate"
|
||||||
type="application/atom+xml"
|
type="application/atom+xml"
|
||||||
title="<?php echo Util::escape_html($config->siteTitle) ?> Atom Feed"
|
title="<?php echo Util::escape_html($settings->siteTitle) ?> Atom Feed"
|
||||||
href="<?php echo Util::escape_html($config->baseUrl . $config->basePath)?>feed/atom/">
|
href="<?php echo Util::escape_html($settings->baseUrl . $settings->basePath)?>feed/atom/">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<?php include TEMPLATES_DIR . '/partials/navbar.php'?>
|
<?php include TEMPLATES_DIR . '/partials/navbar.php'?>
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<?php /** @var ConfigModel $config */ ?>
|
<?php /** @var SettingsModel $settings */ ?>
|
||||||
<?php /** @var UserModel $user */ ?>
|
<?php /** @var UserModel $user */ ?>
|
||||||
<?php /** @var isSetup bool */ ?>
|
<?php /** @var isSetup bool */ ?>
|
||||||
<h1><?php if ($isSetup): ?>Setup<?php else: ?>Admin<?php endif; ?></h1>
|
<h1><?php if ($isSetup): ?>Setup<?php else: ?>Admin<?php endif; ?></h1>
|
||||||
<main>
|
<main>
|
||||||
<form
|
<form
|
||||||
action="<?php echo Util::buildRelativeUrl($config->basePath, ($isSetup ? 'setup' : 'admin')) ?>"
|
action="<?php echo Util::buildRelativeUrl($settings->basePath, ($isSetup ? 'setup' : 'admin')) ?>"
|
||||||
method="post">
|
method="post">
|
||||||
<input type="hidden" name="csrf_token" value="<?= Util::escape_html($_SESSION['csrf_token']) ?>">
|
<input type="hidden" name="csrf_token" value="<?= Util::escape_html($_SESSION['csrf_token']) ?>">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
@ -36,43 +36,43 @@
|
|||||||
<input type="text"
|
<input type="text"
|
||||||
id="site_title"
|
id="site_title"
|
||||||
name="site_title"
|
name="site_title"
|
||||||
value="<?= Util::escape_html($config->siteTitle) ?>"
|
value="<?= Util::escape_html($settings->siteTitle) ?>"
|
||||||
required>
|
required>
|
||||||
<label for="site_description">Description <span class=required>*</span></label>
|
<label for="site_description">Description <span class=required>*</span></label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
id="site_description"
|
id="site_description"
|
||||||
name="site_description"
|
name="site_description"
|
||||||
value="<?= Util::escape_html($config->siteDescription) ?>">
|
value="<?= Util::escape_html($settings->siteDescription) ?>">
|
||||||
<label for="base_url">Base URL <span class=required>*</span></label>
|
<label for="base_url">Base URL <span class=required>*</span></label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
id="base_url"
|
id="base_url"
|
||||||
name="base_url"
|
name="base_url"
|
||||||
value="<?= Util::escape_html($config->baseUrl) ?>"
|
value="<?= Util::escape_html($settings->baseUrl) ?>"
|
||||||
required>
|
required>
|
||||||
<label for="base_path">Base path <span class=required>*</span></label>
|
<label for="base_path">Base path <span class=required>*</span></label>
|
||||||
<input type="text"
|
<input type="text"
|
||||||
id="base_path"
|
id="base_path"
|
||||||
name="base_path"
|
name="base_path"
|
||||||
value="<?= Util::escape_html($config->basePath) ?>"
|
value="<?= Util::escape_html($settings->basePath) ?>"
|
||||||
required>
|
required>
|
||||||
<label for="items_per_page">Ticks per page (max 50) <span class=required>*</span></label>
|
<label for="items_per_page">Ticks per page (max 50) <span class=required>*</span></label>
|
||||||
<input type="number"
|
<input type="number"
|
||||||
id="items_per_page"
|
id="items_per_page"
|
||||||
name="items_per_page"
|
name="items_per_page"
|
||||||
value="<?= $config->itemsPerPage ?>" min="1" max="50"
|
value="<?= $settings->itemsPerPage ?>" min="1" max="50"
|
||||||
required>
|
required>
|
||||||
<label for="strict_accessibility">Strict accessibility</label>
|
<label for="strict_accessibility">Strict accessibility</label>
|
||||||
<input type="checkbox"
|
<input type="checkbox"
|
||||||
id="strict_accessibility"
|
id="strict_accessibility"
|
||||||
name="strict_accessibility"
|
name="strict_accessibility"
|
||||||
value="1"
|
value="1"
|
||||||
<?php if ($config->strictAccessibility): ?> checked <?php endif; ?>>
|
<?php if ($settings->strictAccessibility): ?> checked <?php endif; ?>>
|
||||||
<label for="strict_accessibility">Log Level</label>
|
<label for="strict_accessibility">Log Level</label>
|
||||||
<select id="log_level" name="log_level">
|
<select id="log_level" name="log_level">
|
||||||
<option value="1" <?= ($config->logLevel ?? 2) == 1 ? 'selected' : '' ?>>DEBUG</option>
|
<option value="1" <?= ($settings->logLevel ?? 2) == 1 ? 'selected' : '' ?>>DEBUG</option>
|
||||||
<option value="2" <?= ($config->logLevel ?? 2) == 2 ? 'selected' : '' ?>>INFO</option>
|
<option value="2" <?= ($settings->logLevel ?? 2) == 2 ? 'selected' : '' ?>>INFO</option>
|
||||||
<option value="3" <?= ($config->logLevel ?? 2) == 3 ? 'selected' : '' ?>>WARNING</option>
|
<option value="3" <?= ($settings->logLevel ?? 2) == 3 ? 'selected' : '' ?>>WARNING</option>
|
||||||
<option value="4" <?= ($config->logLevel ?? 2) == 4 ? 'selected' : '' ?>>ERROR</option>
|
<option value="4" <?= ($settings->logLevel ?? 2) == 4 ? 'selected' : '' ?>>ERROR</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
<?php /** @var ConfigModel $config */ ?>
|
<?php /** @var SettingsModel $settings */ ?>
|
||||||
<?php /** @var Array $customCss */ ?>
|
<?php /** @var Array $customCss */ ?>
|
||||||
<h1>CSS Management</h1>
|
<h1>CSS Management</h1>
|
||||||
<main>
|
<main>
|
||||||
<form action="<?= Util::buildRelativeUrl($config->basePath, 'admin/css') ?>" method="post" enctype="multipart/form-data">
|
<form action="<?= Util::buildRelativeUrl($settings->basePath, 'admin/css') ?>" method="post" enctype="multipart/form-data">
|
||||||
<input type="hidden" name="csrf_token" value="<?= Util::escape_html($_SESSION['csrf_token']) ?>">
|
<input type="hidden" name="csrf_token" value="<?= Util::escape_html($_SESSION['csrf_token']) ?>">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Manage</legend>
|
<legend>Manage</legend>
|
||||||
<div class="fieldset-items">
|
<div class="fieldset-items">
|
||||||
<label for="selectCssFile">Select CSS File</label>
|
<label for="selectCssFile">Select CSS File</label>
|
||||||
<select id="selectCssFile" name="selectCssFile">
|
<select id="selectCssFile" name="selectCssFile">
|
||||||
<option value="" <?php if(!$config->cssId): ?>selected<?php endif; ?>>Default</option>
|
<option value="" <?php if(!$settings->cssId): ?>selected<?php endif; ?>>Default</option>
|
||||||
<?php foreach ($customCss as $cssFile): ?>
|
<?php foreach ($customCss as $cssFile): ?>
|
||||||
<?php
|
<?php
|
||||||
if ((int) $cssFile['id'] == $config->cssId){
|
if ((int) $cssFile['id'] == $settings->cssId){
|
||||||
$cssDescription = $cssFile['description'];
|
$cssDescription = $cssFile['description'];
|
||||||
$selected = "selected";
|
$selected = "selected";
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<?php /** @var ConfigModel $config */ ?>
|
<?php /** @var SettingsModel $settings */ ?>
|
||||||
<?php /** @var array $emojiList */ ?>
|
<?php /** @var array $emojiList */ ?>
|
||||||
<h1>Emoji Management</h1>
|
<h1>Emoji Management</h1>
|
||||||
<main>
|
<main>
|
||||||
<form action="<?= Util::buildRelativeUrl($config->basePath, 'admin/emoji') ?>" method="post" enctype="multipart/form-data">
|
<form action="<?= Util::buildRelativeUrl($settings->basePath, 'admin/emoji') ?>" method="post" enctype="multipart/form-data">
|
||||||
<input type="hidden" name="csrf_token" value="<?= Util::escape_html($_SESSION['csrf_token']) ?>">
|
<input type="hidden" name="csrf_token" value="<?= Util::escape_html($_SESSION['csrf_token']) ?>">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Add Emoji</legend>
|
<legend>Add Emoji</legend>
|
||||||
@ -24,7 +24,7 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
<?php if (!empty($emojiList)): ?>
|
<?php if (!empty($emojiList)): ?>
|
||||||
<form action="<?= Util::buildRelativeUrl($config->basePath, 'admin/emoji') ?>" method="post" enctype="multipart/form-data">
|
<form action="<?= Util::buildRelativeUrl($settings->basePath, 'admin/emoji') ?>" method="post" enctype="multipart/form-data">
|
||||||
<input type="hidden" name="csrf_token" value="<?= Util::escape_html($_SESSION['csrf_token']) ?>">
|
<input type="hidden" name="csrf_token" value="<?= Util::escape_html($_SESSION['csrf_token']) ?>">
|
||||||
<fieldset class="delete-emoji-fieldset">
|
<fieldset class="delete-emoji-fieldset">
|
||||||
<legend>Delete Emoji</legend>
|
<legend>Delete Emoji</legend>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<?php /** @var bool $isLoggedIn */ ?>
|
<?php /** @var bool $isLoggedIn */ ?>
|
||||||
<?php /** @var ConfigModel $config */ ?>
|
<?php /** @var SettingsModel $settings */ ?>
|
||||||
<?php /** @var UserModel $user */ ?>
|
<?php /** @var UserModel $user */ ?>
|
||||||
<?php /** @var string $tickList */ ?>
|
<?php /** @var string $tickList */ ?>
|
||||||
<div class="home-container">
|
<div class="home-container">
|
||||||
@ -13,8 +13,8 @@
|
|||||||
</span>
|
</span>
|
||||||
<?php if (Session::isLoggedIn()): ?>
|
<?php if (Session::isLoggedIn()): ?>
|
||||||
<a
|
<a
|
||||||
<?php if($config->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
<?php if($settings->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
||||||
href="<?= Util::escape_html(Util::buildRelativeUrl($config->basePath, 'mood')) ?>"
|
href="<?= Util::escape_html(Util::buildRelativeUrl($settings->basePath, 'mood')) ?>"
|
||||||
class="change-mood">Change mood</a>
|
class="change-mood">Change mood</a>
|
||||||
<?php endif ?>
|
<?php endif ?>
|
||||||
</dd>
|
</dd>
|
||||||
@ -48,7 +48,7 @@
|
|||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</aside>
|
</aside>
|
||||||
<main id="ticks">
|
<main id="ticks">
|
||||||
<h1 class="site-description"><?= Util::escape_html($config->siteDescription) ?></h1>
|
<h1 class="site-description"><?= Util::escape_html($settings->siteDescription) ?></h1>
|
||||||
<?php echo $tickList ?>
|
<?php echo $tickList ?>
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<?php /** @var ConfigModel $config */ ?>
|
<?php /** @var SettingsModel $settings */ ?>
|
||||||
<?php /** @var string $csrf_token */ ?>
|
<?php /** @var string $csrf_token */ ?>
|
||||||
<?php /** @var string $error */ ?>
|
<?php /** @var string $error */ ?>
|
||||||
<h2>Login</h2>
|
<h2>Login</h2>
|
||||||
<form method="post" action="<?= Util::buildRelativeUrl($config->basePath, 'login') ?>">
|
<form method="post" action="<?= Util::buildRelativeUrl($settings->basePath, 'login') ?>">
|
||||||
<div class="fieldset-items">
|
<div class="fieldset-items">
|
||||||
<input type="hidden" name="csrf_token" value="<?= Util::escape_html($csrf_token) ?>">
|
<input type="hidden" name="csrf_token" value="<?= Util::escape_html($csrf_token) ?>">
|
||||||
<label for="username">Username:</label>
|
<label for="username">Username:</label>
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<?php /** @var ConfigModel $config */ ?>
|
<?php /** @var SettingsModel $settings */ ?>
|
||||||
<?php /** @var array $logEntries */ ?>
|
<?php /** @var array $logEntries */ ?>
|
||||||
<?php /** @var array $availableRoutes */ ?>
|
<?php /** @var array $availableRoutes */ ?>
|
||||||
<?php /** @var array $availableLevels */ ?>
|
<?php /** @var array $availableLevels */ ?>
|
||||||
@ -8,7 +8,7 @@
|
|||||||
<main>
|
<main>
|
||||||
<!-- Filters -->
|
<!-- Filters -->
|
||||||
<div class="log-filters">
|
<div class="log-filters">
|
||||||
<form method="get" action="<?= Util::buildRelativeUrl($config->basePath, 'admin/logs') ?>">
|
<form method="get" action="<?= Util::buildRelativeUrl($settings->basePath, 'admin/logs') ?>">
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend>Filter Logs</legend>
|
<legend>Filter Logs</legend>
|
||||||
<div class="fieldset-items">
|
<div class="fieldset-items">
|
||||||
@ -35,7 +35,7 @@
|
|||||||
</select>
|
</select>
|
||||||
|
|
||||||
<div></div><button type="submit">Filter</button>
|
<div></div><button type="submit">Filter</button>
|
||||||
<div></div><a href="<?= Util::buildRelativeUrl($config->basePath, 'admin/logs') ?>">Clear</a>
|
<div></div><a href="<?= Util::buildRelativeUrl($settings->basePath, 'admin/logs') ?>">Clear</a>
|
||||||
</div>
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
</form>
|
</form>
|
||||||
|
@ -1,35 +1,35 @@
|
|||||||
<?php /** @var ConfigModel $config */ ?>
|
<?php /** @var SettingsModel $settings */ ?>
|
||||||
<?php /* https://www.w3schools.com/howto/howto_css_dropdown.asp */ ?>
|
<?php /* https://www.w3schools.com/howto/howto_css_dropdown.asp */ ?>
|
||||||
<nav aria-label="Main navigation">
|
<nav aria-label="Main navigation">
|
||||||
<a <?php if($config->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
<a <?php if($settings->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
||||||
href="<?= Util::escape_html(Util::buildRelativeUrl($config->basePath)) ?>">home</a>
|
href="<?= Util::escape_html(Util::buildRelativeUrl($settings->basePath)) ?>">home</a>
|
||||||
<details>
|
<details>
|
||||||
<summary aria-haspopup="true">feeds</summary>
|
<summary aria-haspopup="true">feeds</summary>
|
||||||
<div class="dropdown-items">
|
<div class="dropdown-items">
|
||||||
<a <?php if($config->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
<a <?php if($settings->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
||||||
href="<?= Util::escape_html(Util::buildRelativeUrl($config->basePath, 'feed/rss')) ?>">rss</a>
|
href="<?= Util::escape_html(Util::buildRelativeUrl($settings->basePath, 'feed/rss')) ?>">rss</a>
|
||||||
<a <?php if($config->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
<a <?php if($settings->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
||||||
href="<?= Util::escape_html(Util::buildRelativeUrl($config->basePath, 'feed/atom')) ?>">atom</a>
|
href="<?= Util::escape_html(Util::buildRelativeUrl($settings->basePath, 'feed/atom')) ?>">atom</a>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<?php if (!Session::isLoggedIn()): ?>
|
<?php if (!Session::isLoggedIn()): ?>
|
||||||
<a tabindex="0"
|
<a tabindex="0"
|
||||||
href="<?= Util::escape_html(Util::buildRelativeUrl($config->basePath, 'login')) ?>">login</a>
|
href="<?= Util::escape_html(Util::buildRelativeUrl($settings->basePath, 'login')) ?>">login</a>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
<details>
|
<details>
|
||||||
<summary aria-haspopup="true">admin</summary>
|
<summary aria-haspopup="true">admin</summary>
|
||||||
<div class="dropdown-items">
|
<div class="dropdown-items">
|
||||||
<a <?php if($config->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
<a <?php if($settings->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
||||||
href="<?= Util::escape_html(Util::buildRelativeUrl($config->basePath, 'admin')) ?>">settings</a>
|
href="<?= Util::escape_html(Util::buildRelativeUrl($settings->basePath, 'admin')) ?>">settings</a>
|
||||||
<a <?php if($config->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
<a <?php if($settings->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
||||||
href="<?= Util::escape_html(Util::buildRelativeUrl($config->basePath, 'admin/css')) ?>">css</a>
|
href="<?= Util::escape_html(Util::buildRelativeUrl($settings->basePath, 'admin/css')) ?>">css</a>
|
||||||
<a <?php if($config->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
<a <?php if($settings->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
||||||
href="<?= Util::escape_html(Util::buildRelativeUrl($config->basePath, 'admin/emoji')) ?>">emoji</a>
|
href="<?= Util::escape_html(Util::buildRelativeUrl($settings->basePath, 'admin/emoji')) ?>">emoji</a>
|
||||||
<a <?php if($config->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
<a <?php if($settings->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
||||||
href="<?= Util::escape_html(Util::buildRelativeUrl($config->basePath, 'admin/logs')) ?>">logs</a>
|
href="<?= Util::escape_html(Util::buildRelativeUrl($settings->basePath, 'admin/logs')) ?>">logs</a>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
<a <?php if($config->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
<a <?php if($settings->strictAccessibility): ?>tabindex="0"<?php endif; ?>
|
||||||
href="<?= Util::escape_html(Util::buildRelativeUrl($config->basePath, 'logout')) ?>">logout</a>
|
href="<?= Util::escape_html(Util::buildRelativeUrl($settings->basePath, 'logout')) ?>">logout</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
</nav>
|
</nav>
|
@ -7,32 +7,32 @@ use PHPUnit\Framework\TestCase;
|
|||||||
class AdminControllerTest extends TestCase
|
class AdminControllerTest extends TestCase
|
||||||
{
|
{
|
||||||
private PDO $mockPdo;
|
private PDO $mockPdo;
|
||||||
private ConfigModel $config;
|
private SettingsModel $settings;
|
||||||
private UserModel $user;
|
private UserModel $user;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
// Create mock PDO
|
// Create mock PDO
|
||||||
$this->mockPdo = $this->createMock(PDO::class);
|
$this->mockPdo = $this->createMock(PDO::class);
|
||||||
|
|
||||||
// Create real config and user objects with mocked PDO
|
// Create real config and user objects with mocked PDO
|
||||||
$this->config = new ConfigModel($this->mockPdo);
|
$this->settings = new SettingsModel($this->mockPdo);
|
||||||
$this->config->siteTitle = 'Test Site';
|
$this->settings->siteTitle = 'Test Site';
|
||||||
$this->config->siteDescription = 'Test Description';
|
$this->settings->siteDescription = 'Test Description';
|
||||||
$this->config->baseUrl = 'https://example.com';
|
$this->settings->baseUrl = 'https://example.com';
|
||||||
$this->config->basePath = '/tkr';
|
$this->settings->basePath = '/tkr';
|
||||||
$this->config->itemsPerPage = 10;
|
$this->settings->itemsPerPage = 10;
|
||||||
|
|
||||||
$this->user = new UserModel($this->mockPdo);
|
$this->user = new UserModel($this->mockPdo);
|
||||||
$this->user->username = 'testuser';
|
$this->user->username = 'testuser';
|
||||||
$this->user->displayName = 'Test User';
|
$this->user->displayName = 'Test User';
|
||||||
$this->user->website = 'https://example.com';
|
$this->user->website = 'https://example.com';
|
||||||
|
|
||||||
// Set up global $app for simplified dependency access
|
// Set up global $app for simplified dependency access
|
||||||
global $app;
|
global $app;
|
||||||
$app = [
|
$app = [
|
||||||
'db' => $this->mockPdo,
|
'db' => $this->mockPdo,
|
||||||
'config' => $this->config,
|
'settings' => $this->settings,
|
||||||
'user' => $this->user,
|
'user' => $this->user,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -41,14 +41,14 @@ class AdminControllerTest extends TestCase
|
|||||||
{
|
{
|
||||||
$controller = new AdminController();
|
$controller = new AdminController();
|
||||||
$data = $controller->getAdminData(false);
|
$data = $controller->getAdminData(false);
|
||||||
|
|
||||||
// Should return proper structure
|
// Should return proper structure
|
||||||
$this->assertArrayHasKey('config', $data);
|
$this->assertArrayHasKey('settings', $data);
|
||||||
$this->assertArrayHasKey('user', $data);
|
$this->assertArrayHasKey('user', $data);
|
||||||
$this->assertArrayHasKey('isSetup', $data);
|
$this->assertArrayHasKey('isSetup', $data);
|
||||||
|
|
||||||
// Should be the injected instances
|
// Should be the injected instances
|
||||||
$this->assertSame($this->config, $data['config']);
|
$this->assertSame($this->settings, $data['settings']);
|
||||||
$this->assertSame($this->user, $data['user']);
|
$this->assertSame($this->user, $data['user']);
|
||||||
$this->assertFalse($data['isSetup']);
|
$this->assertFalse($data['isSetup']);
|
||||||
}
|
}
|
||||||
@ -57,14 +57,14 @@ class AdminControllerTest extends TestCase
|
|||||||
{
|
{
|
||||||
$controller = new AdminController();
|
$controller = new AdminController();
|
||||||
$data = $controller->getAdminData(true);
|
$data = $controller->getAdminData(true);
|
||||||
|
|
||||||
// Should return proper structure
|
// Should return proper structure
|
||||||
$this->assertArrayHasKey('config', $data);
|
$this->assertArrayHasKey('settings', $data);
|
||||||
$this->assertArrayHasKey('user', $data);
|
$this->assertArrayHasKey('user', $data);
|
||||||
$this->assertArrayHasKey('isSetup', $data);
|
$this->assertArrayHasKey('isSetup', $data);
|
||||||
|
|
||||||
// Should be the injected instances
|
// Should be the injected instances
|
||||||
$this->assertSame($this->config, $data['config']);
|
$this->assertSame($this->settings, $data['settings']);
|
||||||
$this->assertSame($this->user, $data['user']);
|
$this->assertSame($this->user, $data['user']);
|
||||||
$this->assertTrue($data['isSetup']);
|
$this->assertTrue($data['isSetup']);
|
||||||
}
|
}
|
||||||
@ -73,7 +73,7 @@ class AdminControllerTest extends TestCase
|
|||||||
{
|
{
|
||||||
$controller = new AdminController();
|
$controller = new AdminController();
|
||||||
$result = $controller->saveSettings([], false);
|
$result = $controller->saveSettings([], false);
|
||||||
|
|
||||||
$this->assertFalse($result['success']);
|
$this->assertFalse($result['success']);
|
||||||
$this->assertContains('No data provided', $result['errors']);
|
$this->assertContains('No data provided', $result['errors']);
|
||||||
}
|
}
|
||||||
@ -81,7 +81,7 @@ class AdminControllerTest extends TestCase
|
|||||||
public function testProcessSettingsSaveValidationErrors(): void
|
public function testProcessSettingsSaveValidationErrors(): void
|
||||||
{
|
{
|
||||||
$controller = new AdminController();
|
$controller = new AdminController();
|
||||||
|
|
||||||
// Test data with multiple validation errors
|
// Test data with multiple validation errors
|
||||||
$postData = [
|
$postData = [
|
||||||
'username' => '', // Missing username
|
'username' => '', // Missing username
|
||||||
@ -94,12 +94,12 @@ class AdminControllerTest extends TestCase
|
|||||||
'password' => 'test123',
|
'password' => 'test123',
|
||||||
'confirm_password' => 'different' // Passwords don't match
|
'confirm_password' => 'different' // Passwords don't match
|
||||||
];
|
];
|
||||||
|
|
||||||
$result = $controller->saveSettings($postData, false);
|
$result = $controller->saveSettings($postData, false);
|
||||||
|
|
||||||
$this->assertFalse($result['success']);
|
$this->assertFalse($result['success']);
|
||||||
$this->assertNotEmpty($result['errors']);
|
$this->assertNotEmpty($result['errors']);
|
||||||
|
|
||||||
// Should have multiple validation errors
|
// Should have multiple validation errors
|
||||||
$this->assertGreaterThan(5, count($result['errors']));
|
$this->assertGreaterThan(5, count($result['errors']));
|
||||||
}
|
}
|
||||||
@ -133,16 +133,16 @@ class AdminControllerTest extends TestCase
|
|||||||
$this->mockPdo->method('query')->willReturn($mockStatement);
|
$this->mockPdo->method('query')->willReturn($mockStatement);
|
||||||
|
|
||||||
// Create models with mocked PDO
|
// Create models with mocked PDO
|
||||||
$config = new ConfigModel($this->mockPdo);
|
$settings = new SettingsModel($this->mockPdo);
|
||||||
$user = new UserModel($this->mockPdo);
|
$user = new UserModel($this->mockPdo);
|
||||||
|
|
||||||
// Update global $app with test models
|
// Update global $app with test models
|
||||||
global $app;
|
global $app;
|
||||||
$app['config'] = $config;
|
$app['settings'] = $settings;
|
||||||
$app['user'] = $user;
|
$app['user'] = $user;
|
||||||
|
|
||||||
$controller = new AdminController();
|
$controller = new AdminController();
|
||||||
|
|
||||||
$postData = [
|
$postData = [
|
||||||
'username' => 'newuser',
|
'username' => 'newuser',
|
||||||
'display_name' => 'New User',
|
'display_name' => 'New User',
|
||||||
@ -155,9 +155,9 @@ class AdminControllerTest extends TestCase
|
|||||||
'strict_accessibility' => 'on',
|
'strict_accessibility' => 'on',
|
||||||
'log_level' => 2
|
'log_level' => 2
|
||||||
];
|
];
|
||||||
|
|
||||||
$result = $controller->saveSettings($postData, false);
|
$result = $controller->saveSettings($postData, false);
|
||||||
|
|
||||||
$this->assertTrue($result['success']);
|
$this->assertTrue($result['success']);
|
||||||
$this->assertEmpty($result['errors']);
|
$this->assertEmpty($result['errors']);
|
||||||
}
|
}
|
||||||
@ -191,20 +191,20 @@ class AdminControllerTest extends TestCase
|
|||||||
$this->mockPdo->expects($this->atLeastOnce())
|
$this->mockPdo->expects($this->atLeastOnce())
|
||||||
->method('prepare')
|
->method('prepare')
|
||||||
->willReturn($mockStatement);
|
->willReturn($mockStatement);
|
||||||
|
|
||||||
$this->mockPdo->method('query')->willReturn($mockStatement);
|
$this->mockPdo->method('query')->willReturn($mockStatement);
|
||||||
|
|
||||||
// Create models with mocked PDO
|
// Create models with mocked PDO
|
||||||
$config = new ConfigModel($this->mockPdo);
|
$settings = new SettingsModel($this->mockPdo);
|
||||||
$user = new UserModel($this->mockPdo);
|
$user = new UserModel($this->mockPdo);
|
||||||
|
|
||||||
// Update global $app with test models
|
// Update global $app with test models
|
||||||
global $app;
|
global $app;
|
||||||
$app['config'] = $config;
|
$app['settings'] = $settings;
|
||||||
$app['user'] = $user;
|
$app['user'] = $user;
|
||||||
|
|
||||||
$controller = new AdminController();
|
$controller = new AdminController();
|
||||||
|
|
||||||
$postData = [
|
$postData = [
|
||||||
'username' => 'testuser',
|
'username' => 'testuser',
|
||||||
'display_name' => 'Test User',
|
'display_name' => 'Test User',
|
||||||
@ -216,9 +216,9 @@ class AdminControllerTest extends TestCase
|
|||||||
'password' => 'newpassword',
|
'password' => 'newpassword',
|
||||||
'confirm_password' => 'newpassword'
|
'confirm_password' => 'newpassword'
|
||||||
];
|
];
|
||||||
|
|
||||||
$result = $controller->saveSettings($postData, false);
|
$result = $controller->saveSettings($postData, false);
|
||||||
|
|
||||||
$this->assertTrue($result['success']);
|
$this->assertTrue($result['success']);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,16 +228,16 @@ class AdminControllerTest extends TestCase
|
|||||||
$this->mockPdo->method('query')
|
$this->mockPdo->method('query')
|
||||||
->willThrowException(new PDOException("Database error"));
|
->willThrowException(new PDOException("Database error"));
|
||||||
|
|
||||||
$config = new ConfigModel($this->mockPdo);
|
$settings = new SettingsModel($this->mockPdo);
|
||||||
$user = new UserModel($this->mockPdo);
|
$user = new UserModel($this->mockPdo);
|
||||||
|
|
||||||
// Update global $app with test models
|
// Update global $app with test models
|
||||||
global $app;
|
global $app;
|
||||||
$app['config'] = $config;
|
$app['settings'] = $settings;
|
||||||
$app['user'] = $user;
|
$app['user'] = $user;
|
||||||
|
|
||||||
$controller = new AdminController();
|
$controller = new AdminController();
|
||||||
|
|
||||||
$postData = [
|
$postData = [
|
||||||
'username' => 'testuser',
|
'username' => 'testuser',
|
||||||
'display_name' => 'Test User',
|
'display_name' => 'Test User',
|
||||||
@ -247,9 +247,9 @@ class AdminControllerTest extends TestCase
|
|||||||
'base_path' => '/tkr',
|
'base_path' => '/tkr',
|
||||||
'items_per_page' => 10
|
'items_per_page' => 10
|
||||||
];
|
];
|
||||||
|
|
||||||
$result = $controller->saveSettings($postData, false);
|
$result = $controller->saveSettings($postData, false);
|
||||||
|
|
||||||
$this->assertFalse($result['success']);
|
$this->assertFalse($result['success']);
|
||||||
$this->assertContains('Failed to save settings', $result['errors']);
|
$this->assertContains('Failed to save settings', $result['errors']);
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@ class FeedControllerTest extends TestCase
|
|||||||
{
|
{
|
||||||
private PDO $mockPdo;
|
private PDO $mockPdo;
|
||||||
private PDOStatement $mockStatement;
|
private PDOStatement $mockStatement;
|
||||||
private ConfigModel $mockConfig;
|
private SettingsModel $mockConfig;
|
||||||
private UserModel $mockUser;
|
private UserModel $mockUser;
|
||||||
private string $tempLogDir;
|
private string $tempLogDir;
|
||||||
|
|
||||||
@ -17,31 +17,31 @@ class FeedControllerTest extends TestCase
|
|||||||
$this->tempLogDir = sys_get_temp_dir() . '/tkr_test_logs_' . uniqid();
|
$this->tempLogDir = sys_get_temp_dir() . '/tkr_test_logs_' . uniqid();
|
||||||
mkdir($this->tempLogDir . '/logs', 0777, true);
|
mkdir($this->tempLogDir . '/logs', 0777, true);
|
||||||
Log::init($this->tempLogDir . '/logs/tkr.log');
|
Log::init($this->tempLogDir . '/logs/tkr.log');
|
||||||
|
|
||||||
// Create mock PDO and PDOStatement
|
// Create mock PDO and PDOStatement
|
||||||
$this->mockStatement = $this->createMock(PDOStatement::class);
|
$this->mockStatement = $this->createMock(PDOStatement::class);
|
||||||
$this->mockPdo = $this->createMock(PDO::class);
|
$this->mockPdo = $this->createMock(PDO::class);
|
||||||
|
|
||||||
// Mock config with feed-relevant properties
|
// Mock config with feed-relevant properties
|
||||||
$this->mockConfig = new ConfigModel($this->mockPdo);
|
$this->mockConfig = new SettingsModel($this->mockPdo);
|
||||||
$this->mockConfig->itemsPerPage = 10;
|
$this->mockConfig->itemsPerPage = 10;
|
||||||
$this->mockConfig->basePath = '/tkr';
|
$this->mockConfig->basePath = '/tkr';
|
||||||
$this->mockConfig->siteTitle = 'Test Site';
|
$this->mockConfig->siteTitle = 'Test Site';
|
||||||
$this->mockConfig->siteDescription = 'Test Description';
|
$this->mockConfig->siteDescription = 'Test Description';
|
||||||
$this->mockConfig->baseUrl = 'https://test.example.com';
|
$this->mockConfig->baseUrl = 'https://test.example.com';
|
||||||
|
|
||||||
// Mock user
|
// Mock user
|
||||||
$this->mockUser = new UserModel($this->mockPdo);
|
$this->mockUser = new UserModel($this->mockPdo);
|
||||||
$this->mockUser->displayName = 'Test User';
|
$this->mockUser->displayName = 'Test User';
|
||||||
|
|
||||||
// Set up global $app for simplified dependency access
|
// Set up global $app for simplified dependency access
|
||||||
global $app;
|
global $app;
|
||||||
$app = [
|
$app = [
|
||||||
'db' => $this->mockPdo,
|
'db' => $this->mockPdo,
|
||||||
'config' => $this->mockConfig,
|
'settings' => $this->mockConfig,
|
||||||
'user' => $this->mockUser,
|
'user' => $this->mockUser,
|
||||||
];
|
];
|
||||||
|
|
||||||
// Set log level on config for Log class
|
// Set log level on config for Log class
|
||||||
$this->mockConfig->logLevel = 1; // Allow DEBUG level logs
|
$this->mockConfig->logLevel = 1; // Allow DEBUG level logs
|
||||||
}
|
}
|
||||||
@ -57,7 +57,7 @@ class FeedControllerTest extends TestCase
|
|||||||
private function deleteDirectory(string $dir): void
|
private function deleteDirectory(string $dir): void
|
||||||
{
|
{
|
||||||
if (!is_dir($dir)) return;
|
if (!is_dir($dir)) return;
|
||||||
|
|
||||||
$files = array_diff(scandir($dir), ['.', '..']);
|
$files = array_diff(scandir($dir), ['.', '..']);
|
||||||
foreach ($files as $file) {
|
foreach ($files as $file) {
|
||||||
$path = $dir . '/' . $file;
|
$path = $dir . '/' . $file;
|
||||||
@ -71,11 +71,11 @@ class FeedControllerTest extends TestCase
|
|||||||
// Mock PDO prepare method to return our mock statement
|
// Mock PDO prepare method to return our mock statement
|
||||||
$this->mockPdo->method('prepare')
|
$this->mockPdo->method('prepare')
|
||||||
->willReturn($this->mockStatement);
|
->willReturn($this->mockStatement);
|
||||||
|
|
||||||
// Mock statement execute method
|
// Mock statement execute method
|
||||||
$this->mockStatement->method('execute')
|
$this->mockStatement->method('execute')
|
||||||
->willReturn(true);
|
->willReturn(true);
|
||||||
|
|
||||||
// Mock statement fetchAll to return our test data
|
// Mock statement fetchAll to return our test data
|
||||||
$this->mockStatement->method('fetchAll')
|
$this->mockStatement->method('fetchAll')
|
||||||
->willReturn($tickData);
|
->willReturn($tickData);
|
||||||
@ -84,16 +84,16 @@ class FeedControllerTest extends TestCase
|
|||||||
public function testControllerInstantiationWithNoTicks(): void
|
public function testControllerInstantiationWithNoTicks(): void
|
||||||
{
|
{
|
||||||
$this->setupMockDatabase([]);
|
$this->setupMockDatabase([]);
|
||||||
|
|
||||||
$controller = new FeedController();
|
$controller = new FeedController();
|
||||||
|
|
||||||
// Verify it was created successfully
|
// Verify it was created successfully
|
||||||
$this->assertInstanceOf(FeedController::class, $controller);
|
$this->assertInstanceOf(FeedController::class, $controller);
|
||||||
|
|
||||||
// Check logs
|
// Check logs
|
||||||
$logFile = $this->tempLogDir . '/logs/tkr.log';
|
$logFile = $this->tempLogDir . '/logs/tkr.log';
|
||||||
$this->assertFileExists($logFile);
|
$this->assertFileExists($logFile);
|
||||||
|
|
||||||
$logContent = file_get_contents($logFile);
|
$logContent = file_get_contents($logFile);
|
||||||
$this->assertStringContainsString('Loaded 0 ticks for feeds', $logContent);
|
$this->assertStringContainsString('Loaded 0 ticks for feeds', $logContent);
|
||||||
}
|
}
|
||||||
@ -104,18 +104,18 @@ class FeedControllerTest extends TestCase
|
|||||||
['id' => 1, 'timestamp' => '2025-01-31 12:00:00', 'tick' => 'First tick'],
|
['id' => 1, 'timestamp' => '2025-01-31 12:00:00', 'tick' => 'First tick'],
|
||||||
['id' => 2, 'timestamp' => '2025-01-31 13:00:00', 'tick' => 'Second tick'],
|
['id' => 2, 'timestamp' => '2025-01-31 13:00:00', 'tick' => 'Second tick'],
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->setupMockDatabase($testTicks);
|
$this->setupMockDatabase($testTicks);
|
||||||
|
|
||||||
$controller = new FeedController();
|
$controller = new FeedController();
|
||||||
|
|
||||||
// Verify it was created successfully
|
// Verify it was created successfully
|
||||||
$this->assertInstanceOf(FeedController::class, $controller);
|
$this->assertInstanceOf(FeedController::class, $controller);
|
||||||
|
|
||||||
// Check logs
|
// Check logs
|
||||||
$logFile = $this->tempLogDir . '/logs/tkr.log';
|
$logFile = $this->tempLogDir . '/logs/tkr.log';
|
||||||
$this->assertFileExists($logFile);
|
$this->assertFileExists($logFile);
|
||||||
|
|
||||||
$logContent = file_get_contents($logFile);
|
$logContent = file_get_contents($logFile);
|
||||||
$this->assertStringContainsString('Loaded 2 ticks for feeds', $logContent);
|
$this->assertStringContainsString('Loaded 2 ticks for feeds', $logContent);
|
||||||
}
|
}
|
||||||
@ -123,18 +123,18 @@ class FeedControllerTest extends TestCase
|
|||||||
public function testControllerCallsDatabaseCorrectly(): void
|
public function testControllerCallsDatabaseCorrectly(): void
|
||||||
{
|
{
|
||||||
$this->setupMockDatabase([]);
|
$this->setupMockDatabase([]);
|
||||||
|
|
||||||
// Verify that PDO prepare is called with the correct SQL for tick loading
|
// Verify that PDO prepare is called with the correct SQL for tick loading
|
||||||
$this->mockPdo->expects($this->once())
|
$this->mockPdo->expects($this->once())
|
||||||
->method('prepare')
|
->method('prepare')
|
||||||
->with('SELECT id, timestamp, tick FROM tick ORDER BY timestamp DESC LIMIT ? OFFSET ?')
|
->with('SELECT id, timestamp, tick FROM tick ORDER BY timestamp DESC LIMIT ? OFFSET ?')
|
||||||
->willReturn($this->mockStatement);
|
->willReturn($this->mockStatement);
|
||||||
|
|
||||||
// Verify that execute is called with correct parameters (page 1, offset 0)
|
// Verify that execute is called with correct parameters (page 1, offset 0)
|
||||||
$this->mockStatement->expects($this->once())
|
$this->mockStatement->expects($this->once())
|
||||||
->method('execute')
|
->method('execute')
|
||||||
->with([10, 0]); // itemsPerPage=10, page 1 = offset 0
|
->with([10, 0]); // itemsPerPage=10, page 1 = offset 0
|
||||||
|
|
||||||
new FeedController();
|
new FeedController();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,16 +143,16 @@ class FeedControllerTest extends TestCase
|
|||||||
$testTicks = [
|
$testTicks = [
|
||||||
['id' => 1, 'timestamp' => '2025-01-31 12:00:00', 'tick' => 'Test tick']
|
['id' => 1, 'timestamp' => '2025-01-31 12:00:00', 'tick' => 'Test tick']
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->setupMockDatabase($testTicks);
|
$this->setupMockDatabase($testTicks);
|
||||||
|
|
||||||
$controller = new FeedController();
|
$controller = new FeedController();
|
||||||
|
|
||||||
// Capture output to prevent headers/content from affecting test
|
// Capture output to prevent headers/content from affecting test
|
||||||
ob_start();
|
ob_start();
|
||||||
$controller->rss();
|
$controller->rss();
|
||||||
ob_end_clean();
|
ob_end_clean();
|
||||||
|
|
||||||
// Check logs for RSS generation
|
// Check logs for RSS generation
|
||||||
$logFile = $this->tempLogDir . '/logs/tkr.log';
|
$logFile = $this->tempLogDir . '/logs/tkr.log';
|
||||||
$logContent = file_get_contents($logFile);
|
$logContent = file_get_contents($logFile);
|
||||||
@ -165,16 +165,16 @@ class FeedControllerTest extends TestCase
|
|||||||
['id' => 1, 'timestamp' => '2025-01-31 12:00:00', 'tick' => 'Test tick'],
|
['id' => 1, 'timestamp' => '2025-01-31 12:00:00', 'tick' => 'Test tick'],
|
||||||
['id' => 2, 'timestamp' => '2025-01-31 13:00:00', 'tick' => 'Another tick']
|
['id' => 2, 'timestamp' => '2025-01-31 13:00:00', 'tick' => 'Another tick']
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->setupMockDatabase($testTicks);
|
$this->setupMockDatabase($testTicks);
|
||||||
|
|
||||||
$controller = new FeedController();
|
$controller = new FeedController();
|
||||||
|
|
||||||
// Capture output to prevent headers/content from affecting test
|
// Capture output to prevent headers/content from affecting test
|
||||||
ob_start();
|
ob_start();
|
||||||
$controller->atom();
|
$controller->atom();
|
||||||
ob_end_clean();
|
ob_end_clean();
|
||||||
|
|
||||||
// Check logs for Atom generation
|
// Check logs for Atom generation
|
||||||
$logFile = $this->tempLogDir . '/logs/tkr.log';
|
$logFile = $this->tempLogDir . '/logs/tkr.log';
|
||||||
$logContent = file_get_contents($logFile);
|
$logContent = file_get_contents($logFile);
|
||||||
|
@ -7,33 +7,33 @@ class HomeControllerTest extends TestCase
|
|||||||
{
|
{
|
||||||
private PDO $mockPdo;
|
private PDO $mockPdo;
|
||||||
private PDOStatement $mockStatement;
|
private PDOStatement $mockStatement;
|
||||||
private ConfigModel $mockConfig;
|
private SettingsModel $mockConfig;
|
||||||
private UserModel $mockUser;
|
private UserModel $mockUser;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
// Reset Log state to prevent test pollution
|
// Reset Log state to prevent test pollution
|
||||||
Log::init(sys_get_temp_dir() . '/tkr_controller_test.log');
|
Log::init(sys_get_temp_dir() . '/tkr_controller_test.log');
|
||||||
|
|
||||||
// Create mock PDO and PDOStatement
|
// Create mock PDO and PDOStatement
|
||||||
$this->mockStatement = $this->createMock(PDOStatement::class);
|
$this->mockStatement = $this->createMock(PDOStatement::class);
|
||||||
$this->mockPdo = $this->createMock(PDO::class);
|
$this->mockPdo = $this->createMock(PDO::class);
|
||||||
|
|
||||||
// Mock config
|
// Mock config
|
||||||
$this->mockConfig = new ConfigModel($this->mockPdo);
|
$this->mockConfig = new SettingsModel($this->mockPdo);
|
||||||
$this->mockConfig->itemsPerPage = 10;
|
$this->mockConfig->itemsPerPage = 10;
|
||||||
$this->mockConfig->basePath = '/tkr';
|
$this->mockConfig->basePath = '/tkr';
|
||||||
|
|
||||||
// Mock user
|
// Mock user
|
||||||
$this->mockUser = new UserModel($this->mockPdo);
|
$this->mockUser = new UserModel($this->mockPdo);
|
||||||
$this->mockUser->displayName = 'Test User';
|
$this->mockUser->displayName = 'Test User';
|
||||||
$this->mockUser->mood = '😊';
|
$this->mockUser->mood = '😊';
|
||||||
|
|
||||||
// Set up global $app for simplified dependency access
|
// Set up global $app for simplified dependency access
|
||||||
global $app;
|
global $app;
|
||||||
$app = [
|
$app = [
|
||||||
'db' => $this->mockPdo,
|
'db' => $this->mockPdo,
|
||||||
'config' => $this->mockConfig,
|
'settings' => $this->mockConfig,
|
||||||
'user' => $this->mockUser,
|
'user' => $this->mockUser,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -43,11 +43,11 @@ class HomeControllerTest extends TestCase
|
|||||||
// Mock PDO prepare method to return our mock statement
|
// Mock PDO prepare method to return our mock statement
|
||||||
$this->mockPdo->method('prepare')
|
$this->mockPdo->method('prepare')
|
||||||
->willReturn($this->mockStatement);
|
->willReturn($this->mockStatement);
|
||||||
|
|
||||||
// Mock statement execute method
|
// Mock statement execute method
|
||||||
$this->mockStatement->method('execute')
|
$this->mockStatement->method('execute')
|
||||||
->willReturn(true);
|
->willReturn(true);
|
||||||
|
|
||||||
// Mock statement fetchAll to return our test data
|
// Mock statement fetchAll to return our test data
|
||||||
$this->mockStatement->method('fetchAll')
|
$this->mockStatement->method('fetchAll')
|
||||||
->willReturn($tickData);
|
->willReturn($tickData);
|
||||||
@ -59,7 +59,7 @@ class HomeControllerTest extends TestCase
|
|||||||
// Mock successful insert
|
// Mock successful insert
|
||||||
$this->mockPdo->method('prepare')
|
$this->mockPdo->method('prepare')
|
||||||
->willReturn($this->mockStatement);
|
->willReturn($this->mockStatement);
|
||||||
|
|
||||||
$this->mockStatement->method('execute')
|
$this->mockStatement->method('execute')
|
||||||
->willReturn(true);
|
->willReturn(true);
|
||||||
} else {
|
} else {
|
||||||
@ -72,19 +72,19 @@ class HomeControllerTest extends TestCase
|
|||||||
public function testGetHomeDataWithNoTicks(): void
|
public function testGetHomeDataWithNoTicks(): void
|
||||||
{
|
{
|
||||||
$this->setupMockDatabase([]); // Empty array = no ticks
|
$this->setupMockDatabase([]); // Empty array = no ticks
|
||||||
|
|
||||||
$controller = new HomeController();
|
$controller = new HomeController();
|
||||||
$data = $controller->getHomeData(1);
|
$data = $controller->getHomeData(1);
|
||||||
|
|
||||||
// Should return proper structure
|
// Should return proper structure
|
||||||
$this->assertArrayHasKey('config', $data);
|
$this->assertArrayHasKey('settings', $data);
|
||||||
$this->assertArrayHasKey('user', $data);
|
$this->assertArrayHasKey('user', $data);
|
||||||
$this->assertArrayHasKey('tickList', $data);
|
$this->assertArrayHasKey('tickList', $data);
|
||||||
|
|
||||||
// Config and user should be the injected instances
|
// Config and user should be the injected instances
|
||||||
$this->assertSame($this->mockConfig, $data['config']);
|
$this->assertSame($this->mockConfig, $data['settings']);
|
||||||
$this->assertSame($this->mockUser, $data['user']);
|
$this->assertSame($this->mockUser, $data['user']);
|
||||||
|
|
||||||
// Should have tick list HTML (even if empty)
|
// Should have tick list HTML (even if empty)
|
||||||
$this->assertIsString($data['tickList']);
|
$this->assertIsString($data['tickList']);
|
||||||
}
|
}
|
||||||
@ -97,17 +97,17 @@ class HomeControllerTest extends TestCase
|
|||||||
['id' => 2, 'timestamp' => '2025-01-31 13:00:00', 'tick' => 'Second tick'],
|
['id' => 2, 'timestamp' => '2025-01-31 13:00:00', 'tick' => 'Second tick'],
|
||||||
['id' => 3, 'timestamp' => '2025-01-31 14:00:00', 'tick' => 'Third tick'],
|
['id' => 3, 'timestamp' => '2025-01-31 14:00:00', 'tick' => 'Third tick'],
|
||||||
];
|
];
|
||||||
|
|
||||||
$this->setupMockDatabase($testTicks);
|
$this->setupMockDatabase($testTicks);
|
||||||
|
|
||||||
$controller = new HomeController();
|
$controller = new HomeController();
|
||||||
$data = $controller->getHomeData(1);
|
$data = $controller->getHomeData(1);
|
||||||
|
|
||||||
// Should return proper structure
|
// Should return proper structure
|
||||||
$this->assertArrayHasKey('config', $data);
|
$this->assertArrayHasKey('settings', $data);
|
||||||
$this->assertArrayHasKey('user', $data);
|
$this->assertArrayHasKey('user', $data);
|
||||||
$this->assertArrayHasKey('tickList', $data);
|
$this->assertArrayHasKey('tickList', $data);
|
||||||
|
|
||||||
// Should contain tick content in HTML
|
// Should contain tick content in HTML
|
||||||
$this->assertStringContainsString('First tick', $data['tickList']);
|
$this->assertStringContainsString('First tick', $data['tickList']);
|
||||||
$this->assertStringContainsString('Second tick', $data['tickList']);
|
$this->assertStringContainsString('Second tick', $data['tickList']);
|
||||||
@ -117,18 +117,18 @@ class HomeControllerTest extends TestCase
|
|||||||
public function testGetHomeDataCallsDatabaseCorrectly(): void
|
public function testGetHomeDataCallsDatabaseCorrectly(): void
|
||||||
{
|
{
|
||||||
$this->setupMockDatabase([]);
|
$this->setupMockDatabase([]);
|
||||||
|
|
||||||
// Verify that PDO prepare is called with the correct SQL
|
// Verify that PDO prepare is called with the correct SQL
|
||||||
$this->mockPdo->expects($this->once())
|
$this->mockPdo->expects($this->once())
|
||||||
->method('prepare')
|
->method('prepare')
|
||||||
->with('SELECT id, timestamp, tick FROM tick ORDER BY timestamp DESC LIMIT ? OFFSET ?')
|
->with('SELECT id, timestamp, tick FROM tick ORDER BY timestamp DESC LIMIT ? OFFSET ?')
|
||||||
->willReturn($this->mockStatement);
|
->willReturn($this->mockStatement);
|
||||||
|
|
||||||
// Verify that execute is called with correct parameters for page 2
|
// Verify that execute is called with correct parameters for page 2
|
||||||
$this->mockStatement->expects($this->once())
|
$this->mockStatement->expects($this->once())
|
||||||
->method('execute')
|
->method('execute')
|
||||||
->with([10, 10]); // itemsPerPage=10, page 2 = offset 10
|
->with([10, 10]); // itemsPerPage=10, page 2 = offset 10
|
||||||
|
|
||||||
$controller = new HomeController();
|
$controller = new HomeController();
|
||||||
$controller->getHomeData(2); // Page 2
|
$controller->getHomeData(2); // Page 2
|
||||||
}
|
}
|
||||||
@ -136,28 +136,28 @@ class HomeControllerTest extends TestCase
|
|||||||
public function testProcessTickSuccess(): void
|
public function testProcessTickSuccess(): void
|
||||||
{
|
{
|
||||||
$this->setupMockDatabaseForInsert(true);
|
$this->setupMockDatabaseForInsert(true);
|
||||||
|
|
||||||
// Verify the INSERT SQL is called correctly
|
// Verify the INSERT SQL is called correctly
|
||||||
$this->mockPdo->expects($this->once())
|
$this->mockPdo->expects($this->once())
|
||||||
->method('prepare')
|
->method('prepare')
|
||||||
->with('INSERT INTO tick(timestamp, tick) values (?, ?)')
|
->with('INSERT INTO tick(timestamp, tick) values (?, ?)')
|
||||||
->willReturn($this->mockStatement);
|
->willReturn($this->mockStatement);
|
||||||
|
|
||||||
// Verify execute is called with timestamp and content
|
// Verify execute is called with timestamp and content
|
||||||
$this->mockStatement->expects($this->once())
|
$this->mockStatement->expects($this->once())
|
||||||
->method('execute')
|
->method('execute')
|
||||||
->with($this->callback(function($params) {
|
->with($this->callback(function($params) {
|
||||||
// First param should be a timestamp, second should be the tick content
|
// First param should be a timestamp, second should be the tick content
|
||||||
return count($params) === 2
|
return count($params) === 2
|
||||||
&& is_string($params[0])
|
&& is_string($params[0])
|
||||||
&& $params[1] === 'This is a test tick';
|
&& $params[1] === 'This is a test tick';
|
||||||
}));
|
}));
|
||||||
|
|
||||||
$controller = new HomeController();
|
$controller = new HomeController();
|
||||||
$postData = ['new_tick' => 'This is a test tick'];
|
$postData = ['new_tick' => 'This is a test tick'];
|
||||||
|
|
||||||
$result = $controller->processTick($postData);
|
$result = $controller->processTick($postData);
|
||||||
|
|
||||||
$this->assertTrue($result['success']);
|
$this->assertTrue($result['success']);
|
||||||
$this->assertEquals('Tick saved successfully', $result['message']);
|
$this->assertEquals('Tick saved successfully', $result['message']);
|
||||||
}
|
}
|
||||||
@ -166,12 +166,12 @@ class HomeControllerTest extends TestCase
|
|||||||
{
|
{
|
||||||
// PDO shouldn't be called at all for empty content
|
// PDO shouldn't be called at all for empty content
|
||||||
$this->mockPdo->expects($this->never())->method('prepare');
|
$this->mockPdo->expects($this->never())->method('prepare');
|
||||||
|
|
||||||
$controller = new HomeController();
|
$controller = new HomeController();
|
||||||
$postData = ['new_tick' => ' ']; // Just whitespace
|
$postData = ['new_tick' => ' ']; // Just whitespace
|
||||||
|
|
||||||
$result = $controller->processTick($postData);
|
$result = $controller->processTick($postData);
|
||||||
|
|
||||||
$this->assertFalse($result['success']);
|
$this->assertFalse($result['success']);
|
||||||
$this->assertEquals('Empty tick ignored', $result['message']);
|
$this->assertEquals('Empty tick ignored', $result['message']);
|
||||||
}
|
}
|
||||||
@ -180,12 +180,12 @@ class HomeControllerTest extends TestCase
|
|||||||
{
|
{
|
||||||
// PDO shouldn't be called at all for missing field
|
// PDO shouldn't be called at all for missing field
|
||||||
$this->mockPdo->expects($this->never())->method('prepare');
|
$this->mockPdo->expects($this->never())->method('prepare');
|
||||||
|
|
||||||
$controller = new HomeController();
|
$controller = new HomeController();
|
||||||
$postData = []; // No new_tick field
|
$postData = []; // No new_tick field
|
||||||
|
|
||||||
$result = $controller->processTick($postData);
|
$result = $controller->processTick($postData);
|
||||||
|
|
||||||
$this->assertFalse($result['success']);
|
$this->assertFalse($result['success']);
|
||||||
$this->assertEquals('No tick content provided', $result['message']);
|
$this->assertEquals('No tick content provided', $result['message']);
|
||||||
}
|
}
|
||||||
@ -193,31 +193,31 @@ class HomeControllerTest extends TestCase
|
|||||||
public function testProcessTickTrimsWhitespace(): void
|
public function testProcessTickTrimsWhitespace(): void
|
||||||
{
|
{
|
||||||
$this->setupMockDatabaseForInsert(true);
|
$this->setupMockDatabaseForInsert(true);
|
||||||
|
|
||||||
// Verify execute is called with trimmed content
|
// Verify execute is called with trimmed content
|
||||||
$this->mockStatement->expects($this->once())
|
$this->mockStatement->expects($this->once())
|
||||||
->method('execute')
|
->method('execute')
|
||||||
->with($this->callback(function($params) {
|
->with($this->callback(function($params) {
|
||||||
return $params[1] === 'This has whitespace'; // Should be trimmed
|
return $params[1] === 'This has whitespace'; // Should be trimmed
|
||||||
}));
|
}));
|
||||||
|
|
||||||
$controller = new HomeController();
|
$controller = new HomeController();
|
||||||
$postData = ['new_tick' => ' This has whitespace '];
|
$postData = ['new_tick' => ' This has whitespace '];
|
||||||
|
|
||||||
$result = $controller->processTick($postData);
|
$result = $controller->processTick($postData);
|
||||||
|
|
||||||
$this->assertTrue($result['success']);
|
$this->assertTrue($result['success']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testProcessTickHandlesDatabaseError(): void
|
public function testProcessTickHandlesDatabaseError(): void
|
||||||
{
|
{
|
||||||
$this->setupMockDatabaseForInsert(false); // Will throw exception
|
$this->setupMockDatabaseForInsert(false); // Will throw exception
|
||||||
|
|
||||||
$controller = new HomeController();
|
$controller = new HomeController();
|
||||||
$postData = ['new_tick' => 'This will fail'];
|
$postData = ['new_tick' => 'This will fail'];
|
||||||
|
|
||||||
$result = $controller->processTick($postData);
|
$result = $controller->processTick($postData);
|
||||||
|
|
||||||
$this->assertFalse($result['success']);
|
$this->assertFalse($result['success']);
|
||||||
$this->assertEquals('Failed to save tick', $result['message']);
|
$this->assertEquals('Failed to save tick', $result['message']);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ class LogControllerTest extends TestCase
|
|||||||
{
|
{
|
||||||
$this->tempLogDir = sys_get_temp_dir() . '/tkr_test_logs_' . uniqid();
|
$this->tempLogDir = sys_get_temp_dir() . '/tkr_test_logs_' . uniqid();
|
||||||
mkdir($this->tempLogDir, 0777, true);
|
mkdir($this->tempLogDir, 0777, true);
|
||||||
|
|
||||||
$this->testLogFile = $this->tempLogDir . '/logs/tkr.log';
|
$this->testLogFile = $this->tempLogDir . '/logs/tkr.log';
|
||||||
mkdir(dirname($this->testLogFile), 0777, true);
|
mkdir(dirname($this->testLogFile), 0777, true);
|
||||||
|
|
||||||
@ -23,16 +23,16 @@ class LogControllerTest extends TestCase
|
|||||||
|
|
||||||
// Set up global $app for simplified dependency access
|
// Set up global $app for simplified dependency access
|
||||||
$mockPdo = $this->createMock(PDO::class);
|
$mockPdo = $this->createMock(PDO::class);
|
||||||
$mockConfig = new ConfigModel($mockPdo);
|
$mockConfig = new SettingsModel($mockPdo);
|
||||||
$mockConfig->baseUrl = 'https://example.com';
|
$mockConfig->baseUrl = 'https://example.com';
|
||||||
$mockConfig->basePath = '/tkr/';
|
$mockConfig->basePath = '/tkr/';
|
||||||
|
|
||||||
$mockUser = new UserModel($mockPdo);
|
$mockUser = new UserModel($mockPdo);
|
||||||
|
|
||||||
global $app;
|
global $app;
|
||||||
$app = [
|
$app = [
|
||||||
'db' => $mockPdo,
|
'db' => $mockPdo,
|
||||||
'config' => $mockConfig,
|
'settings' => $mockConfig,
|
||||||
'user' => $mockUser,
|
'user' => $mockUser,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ class LogControllerTest extends TestCase
|
|||||||
private function deleteDirectory(string $dir): void
|
private function deleteDirectory(string $dir): void
|
||||||
{
|
{
|
||||||
if (!is_dir($dir)) return;
|
if (!is_dir($dir)) return;
|
||||||
|
|
||||||
$files = array_diff(scandir($dir), ['.', '..']);
|
$files = array_diff(scandir($dir), ['.', '..']);
|
||||||
foreach ($files as $file) {
|
foreach ($files as $file) {
|
||||||
$path = $dir . '/' . $file;
|
$path = $dir . '/' . $file;
|
||||||
@ -64,14 +64,14 @@ class LogControllerTest extends TestCase
|
|||||||
// Uses global $app set up in setUp()
|
// Uses global $app set up in setUp()
|
||||||
$controller = new LogController($this->tempLogDir);
|
$controller = new LogController($this->tempLogDir);
|
||||||
$data = $controller->getLogData();
|
$data = $controller->getLogData();
|
||||||
|
|
||||||
// Should return empty log entries but valid structure
|
// Should return empty log entries but valid structure
|
||||||
$this->assertArrayHasKey('logEntries', $data);
|
$this->assertArrayHasKey('logEntries', $data);
|
||||||
$this->assertArrayHasKey('availableRoutes', $data);
|
$this->assertArrayHasKey('availableRoutes', $data);
|
||||||
$this->assertArrayHasKey('availableLevels', $data);
|
$this->assertArrayHasKey('availableLevels', $data);
|
||||||
$this->assertArrayHasKey('currentLevelFilter', $data);
|
$this->assertArrayHasKey('currentLevelFilter', $data);
|
||||||
$this->assertArrayHasKey('currentRouteFilter', $data);
|
$this->assertArrayHasKey('currentRouteFilter', $data);
|
||||||
|
|
||||||
$this->assertEmpty($data['logEntries']);
|
$this->assertEmpty($data['logEntries']);
|
||||||
$this->assertEmpty($data['availableRoutes']);
|
$this->assertEmpty($data['availableRoutes']);
|
||||||
$this->assertEquals(['DEBUG', 'INFO', 'WARNING', 'ERROR'], $data['availableLevels']);
|
$this->assertEquals(['DEBUG', 'INFO', 'WARNING', 'ERROR'], $data['availableLevels']);
|
||||||
@ -96,15 +96,15 @@ class LogControllerTest extends TestCase
|
|||||||
// Uses global $app set up in setUp()
|
// Uses global $app set up in setUp()
|
||||||
$controller = new LogController($this->tempLogDir);
|
$controller = new LogController($this->tempLogDir);
|
||||||
$data = $controller->getLogData();
|
$data = $controller->getLogData();
|
||||||
|
|
||||||
// Should parse all valid entries and ignore invalid ones
|
// Should parse all valid entries and ignore invalid ones
|
||||||
$this->assertCount(5, $data['logEntries']);
|
$this->assertCount(5, $data['logEntries']);
|
||||||
|
|
||||||
// Verify entries are in reverse chronological order (newest first)
|
// Verify entries are in reverse chronological order (newest first)
|
||||||
$entries = $data['logEntries'];
|
$entries = $data['logEntries'];
|
||||||
$this->assertEquals('Info without route', $entries[0]['message']);
|
$this->assertEquals('Info without route', $entries[0]['message']);
|
||||||
$this->assertEquals('Debug home page', $entries[4]['message']);
|
$this->assertEquals('Debug home page', $entries[4]['message']);
|
||||||
|
|
||||||
// Verify entry structure
|
// Verify entry structure
|
||||||
$firstEntry = $entries[0];
|
$firstEntry = $entries[0];
|
||||||
$this->assertArrayHasKey('timestamp', $firstEntry);
|
$this->assertArrayHasKey('timestamp', $firstEntry);
|
||||||
@ -112,13 +112,13 @@ class LogControllerTest extends TestCase
|
|||||||
$this->assertArrayHasKey('ip', $firstEntry);
|
$this->assertArrayHasKey('ip', $firstEntry);
|
||||||
$this->assertArrayHasKey('route', $firstEntry);
|
$this->assertArrayHasKey('route', $firstEntry);
|
||||||
$this->assertArrayHasKey('message', $firstEntry);
|
$this->assertArrayHasKey('message', $firstEntry);
|
||||||
|
|
||||||
// Test route extraction
|
// Test route extraction
|
||||||
$adminEntry = array_filter($entries, fn($e) => $e['message'] === 'Info admin page');
|
$adminEntry = array_filter($entries, fn($e) => $e['message'] === 'Info admin page');
|
||||||
$adminEntry = array_values($adminEntry)[0];
|
$adminEntry = array_values($adminEntry)[0];
|
||||||
$this->assertEquals('GET /admin', $adminEntry['route']);
|
$this->assertEquals('GET /admin', $adminEntry['route']);
|
||||||
$this->assertEquals('INFO', $adminEntry['level']);
|
$this->assertEquals('INFO', $adminEntry['level']);
|
||||||
|
|
||||||
// Test entry without route
|
// Test entry without route
|
||||||
$noRouteEntry = array_filter($entries, fn($e) => $e['message'] === 'Info without route');
|
$noRouteEntry = array_filter($entries, fn($e) => $e['message'] === 'Info without route');
|
||||||
$noRouteEntry = array_values($noRouteEntry)[0];
|
$noRouteEntry = array_values($noRouteEntry)[0];
|
||||||
@ -138,7 +138,7 @@ class LogControllerTest extends TestCase
|
|||||||
// Uses global $app set up in setUp()
|
// Uses global $app set up in setUp()
|
||||||
$controller = new LogController($this->tempLogDir);
|
$controller = new LogController($this->tempLogDir);
|
||||||
$data = $controller->getLogData('ERROR');
|
$data = $controller->getLogData('ERROR');
|
||||||
|
|
||||||
// Should only include ERROR entries
|
// Should only include ERROR entries
|
||||||
$this->assertCount(1, $data['logEntries']);
|
$this->assertCount(1, $data['logEntries']);
|
||||||
$this->assertEquals('ERROR', $data['logEntries'][0]['level']);
|
$this->assertEquals('ERROR', $data['logEntries'][0]['level']);
|
||||||
@ -159,7 +159,7 @@ class LogControllerTest extends TestCase
|
|||||||
// Uses global $app set up in setUp()
|
// Uses global $app set up in setUp()
|
||||||
$controller = new LogController($this->tempLogDir);
|
$controller = new LogController($this->tempLogDir);
|
||||||
$data = $controller->getLogData('', 'GET /admin');
|
$data = $controller->getLogData('', 'GET /admin');
|
||||||
|
|
||||||
// Should only include GET /admin entries
|
// Should only include GET /admin entries
|
||||||
$this->assertCount(1, $data['logEntries']);
|
$this->assertCount(1, $data['logEntries']);
|
||||||
$this->assertEquals('GET /admin', $data['logEntries'][0]['route']);
|
$this->assertEquals('GET /admin', $data['logEntries'][0]['route']);
|
||||||
@ -171,7 +171,7 @@ class LogControllerTest extends TestCase
|
|||||||
{
|
{
|
||||||
$logContent = implode("\n", [
|
$logContent = implode("\n", [
|
||||||
'[2025-01-31 12:00:00] ERROR: 127.0.0.1 [GET /admin] - Admin error',
|
'[2025-01-31 12:00:00] ERROR: 127.0.0.1 [GET /admin] - Admin error',
|
||||||
'[2025-01-31 12:01:00] INFO: 127.0.0.1 [GET /admin] - Admin info',
|
'[2025-01-31 12:01:00] INFO: 127.0.0.1 [GET /admin] - Admin info',
|
||||||
'[2025-01-31 12:02:00] ERROR: 127.0.0.1 [GET /] - Home error'
|
'[2025-01-31 12:02:00] ERROR: 127.0.0.1 [GET /] - Home error'
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@ -180,7 +180,7 @@ class LogControllerTest extends TestCase
|
|||||||
// Uses global $app set up in setUp()
|
// Uses global $app set up in setUp()
|
||||||
$controller = new LogController($this->tempLogDir);
|
$controller = new LogController($this->tempLogDir);
|
||||||
$data = $controller->getLogData('ERROR', 'GET /admin');
|
$data = $controller->getLogData('ERROR', 'GET /admin');
|
||||||
|
|
||||||
// Should only include entries matching both filters
|
// Should only include entries matching both filters
|
||||||
$this->assertCount(1, $data['logEntries']);
|
$this->assertCount(1, $data['logEntries']);
|
||||||
$this->assertEquals('ERROR', $data['logEntries'][0]['level']);
|
$this->assertEquals('ERROR', $data['logEntries'][0]['level']);
|
||||||
@ -204,7 +204,7 @@ class LogControllerTest extends TestCase
|
|||||||
// Uses global $app set up in setUp()
|
// Uses global $app set up in setUp()
|
||||||
$controller = new LogController($this->tempLogDir);
|
$controller = new LogController($this->tempLogDir);
|
||||||
$data = $controller->getLogData();
|
$data = $controller->getLogData();
|
||||||
|
|
||||||
// Should read from all log files, newest first
|
// Should read from all log files, newest first
|
||||||
$this->assertCount(3, $data['logEntries']);
|
$this->assertCount(3, $data['logEntries']);
|
||||||
$this->assertEquals('Current log entry', $data['logEntries'][0]['message']);
|
$this->assertEquals('Current log entry', $data['logEntries'][0]['message']);
|
||||||
@ -227,7 +227,7 @@ class LogControllerTest extends TestCase
|
|||||||
// Uses global $app set up in setUp()
|
// Uses global $app set up in setUp()
|
||||||
$controller = new LogController($this->tempLogDir);
|
$controller = new LogController($this->tempLogDir);
|
||||||
$data = $controller->getLogData();
|
$data = $controller->getLogData();
|
||||||
|
|
||||||
// Should extract unique routes, sorted
|
// Should extract unique routes, sorted
|
||||||
$expectedRoutes = ['GET /', 'GET /admin', 'POST /admin'];
|
$expectedRoutes = ['GET /', 'GET /admin', 'POST /admin'];
|
||||||
$this->assertEquals($expectedRoutes, $data['availableRoutes']);
|
$this->assertEquals($expectedRoutes, $data['availableRoutes']);
|
||||||
@ -247,7 +247,7 @@ class LogControllerTest extends TestCase
|
|||||||
// Uses global $app set up in setUp()
|
// Uses global $app set up in setUp()
|
||||||
$controller = new LogController($this->tempLogDir);
|
$controller = new LogController($this->tempLogDir);
|
||||||
$data = $controller->getLogData();
|
$data = $controller->getLogData();
|
||||||
|
|
||||||
// Should only include valid entries, ignore invalid ones
|
// Should only include valid entries, ignore invalid ones
|
||||||
$this->assertCount(2, $data['logEntries']);
|
$this->assertCount(2, $data['logEntries']);
|
||||||
$this->assertEquals('Another valid entry', $data['logEntries'][0]['message']);
|
$this->assertEquals('Another valid entry', $data['logEntries'][0]['message']);
|
||||||
|
@ -6,29 +6,29 @@ use PHPUnit\Framework\TestCase;
|
|||||||
class TickControllerTest extends TestCase
|
class TickControllerTest extends TestCase
|
||||||
{
|
{
|
||||||
private $mockPdo;
|
private $mockPdo;
|
||||||
private $config;
|
private $settings;
|
||||||
private $user;
|
private $user;
|
||||||
|
|
||||||
protected function setUp(): void
|
protected function setUp(): void
|
||||||
{
|
{
|
||||||
// Reset Log state to prevent test pollution
|
// Reset Log state to prevent test pollution
|
||||||
Log::init(sys_get_temp_dir() . '/tkr_controller_test.log');
|
Log::init(sys_get_temp_dir() . '/tkr_controller_test.log');
|
||||||
|
|
||||||
// Set up mocks
|
// Set up mocks
|
||||||
$this->mockPdo = $this->createMock(PDO::class);
|
$this->mockPdo = $this->createMock(PDO::class);
|
||||||
|
|
||||||
$this->config = new ConfigModel($this->mockPdo);
|
$this->settings = new SettingsModel($this->mockPdo);
|
||||||
$this->config->baseUrl = 'https://example.com';
|
$this->settings->baseUrl = 'https://example.com';
|
||||||
$this->config->basePath = '/tkr/';
|
$this->settings->basePath = '/tkr/';
|
||||||
$this->config->itemsPerPage = 10;
|
$this->settings->itemsPerPage = 10;
|
||||||
|
|
||||||
$this->user = new UserModel($this->mockPdo);
|
$this->user = new UserModel($this->mockPdo);
|
||||||
|
|
||||||
// Set up global $app for simplified dependency access
|
// Set up global $app for simplified dependency access
|
||||||
global $app;
|
global $app;
|
||||||
$app = [
|
$app = [
|
||||||
'db' => $this->mockPdo,
|
'db' => $this->mockPdo,
|
||||||
'config' => $this->config,
|
'settings' => $this->settings,
|
||||||
'user' => $this->user,
|
'user' => $this->user,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -60,10 +60,10 @@ class TickControllerTest extends TestCase
|
|||||||
|
|
||||||
// Capture output since render() outputs directly
|
// Capture output since render() outputs directly
|
||||||
ob_start();
|
ob_start();
|
||||||
|
|
||||||
$controller = new TickController();
|
$controller = new TickController();
|
||||||
$controller->index(123);
|
$controller->index(123);
|
||||||
|
|
||||||
$output = ob_get_clean();
|
$output = ob_get_clean();
|
||||||
|
|
||||||
// Should not be a 404 or 500 error
|
// Should not be a 404 or 500 error
|
||||||
@ -94,10 +94,10 @@ class TickControllerTest extends TestCase
|
|||||||
|
|
||||||
// Capture output
|
// Capture output
|
||||||
ob_start();
|
ob_start();
|
||||||
|
|
||||||
$controller = new TickController();
|
$controller = new TickController();
|
||||||
$controller->index(999);
|
$controller->index(999);
|
||||||
|
|
||||||
$output = ob_get_clean();
|
$output = ob_get_clean();
|
||||||
|
|
||||||
// Should return 404 error
|
// Should return 404 error
|
||||||
@ -123,10 +123,10 @@ class TickControllerTest extends TestCase
|
|||||||
|
|
||||||
// Capture output
|
// Capture output
|
||||||
ob_start();
|
ob_start();
|
||||||
|
|
||||||
$controller = new TickController();
|
$controller = new TickController();
|
||||||
$controller->index(456);
|
$controller->index(456);
|
||||||
|
|
||||||
$output = ob_get_clean();
|
$output = ob_get_clean();
|
||||||
|
|
||||||
// Should return 404 error for empty data
|
// Should return 404 error for empty data
|
||||||
@ -143,10 +143,10 @@ class TickControllerTest extends TestCase
|
|||||||
|
|
||||||
// Capture output
|
// Capture output
|
||||||
ob_start();
|
ob_start();
|
||||||
|
|
||||||
$controller = new TickController();
|
$controller = new TickController();
|
||||||
$controller->index(123);
|
$controller->index(123);
|
||||||
|
|
||||||
$output = ob_get_clean();
|
$output = ob_get_clean();
|
||||||
|
|
||||||
// Should return 500 error
|
// Should return 500 error
|
||||||
|
@ -7,12 +7,12 @@ class AtomGeneratorTest extends TestCase
|
|||||||
{
|
{
|
||||||
private function createMockConfig() {
|
private function createMockConfig() {
|
||||||
$mockPdo = $this->createMock(PDO::class);
|
$mockPdo = $this->createMock(PDO::class);
|
||||||
$config = new ConfigModel($mockPdo);
|
$settings = new SettingsModel($mockPdo);
|
||||||
$config->siteTitle = 'Test Site';
|
$settings->siteTitle = 'Test Site';
|
||||||
$config->siteDescription = 'Test Description';
|
$settings->siteDescription = 'Test Description';
|
||||||
$config->baseUrl = 'https://example.com';
|
$settings->baseUrl = 'https://example.com';
|
||||||
$config->basePath = '/tkr/';
|
$settings->basePath = '/tkr/';
|
||||||
return $config;
|
return $settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createSampleTicks() {
|
private function createSampleTicks() {
|
||||||
@ -23,10 +23,10 @@ class AtomGeneratorTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testCanGenerateValidAtom() {
|
public function testCanGenerateValidAtom() {
|
||||||
$config = $this->createMockConfig();
|
$settings = $this->createMockConfig();
|
||||||
$ticks = $this->createSampleTicks();
|
$ticks = $this->createSampleTicks();
|
||||||
|
|
||||||
$generator = new AtomGenerator($config, $ticks);
|
$generator = new AtomGenerator($settings, $ticks);
|
||||||
$xml = $generator->generate();
|
$xml = $generator->generate();
|
||||||
|
|
||||||
// Test XML structure
|
// Test XML structure
|
||||||
@ -58,8 +58,8 @@ class AtomGeneratorTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testCanHandleEmptyTickList() {
|
public function testCanHandleEmptyTickList() {
|
||||||
$config = $this->createMockConfig();
|
$settings = $this->createMockConfig();
|
||||||
$generator = new AtomGenerator($config, []);
|
$generator = new AtomGenerator($settings, []);
|
||||||
$xml = $generator->generate();
|
$xml = $generator->generate();
|
||||||
|
|
||||||
// Should still be valid Atom with no entries
|
// Should still be valid Atom with no entries
|
||||||
@ -85,7 +85,7 @@ class AtomGeneratorTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testCanHandleSpecialCharactersAndUnicode() {
|
public function testCanHandleSpecialCharactersAndUnicode() {
|
||||||
$config = $this->createMockConfig();
|
$settings = $this->createMockConfig();
|
||||||
|
|
||||||
// Test various challenging characters
|
// Test various challenging characters
|
||||||
$ticks = [
|
$ticks = [
|
||||||
@ -111,7 +111,7 @@ class AtomGeneratorTest extends TestCase
|
|||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
$generator = new AtomGenerator($config, $ticks);
|
$generator = new AtomGenerator($settings, $ticks);
|
||||||
$xml = $generator->generate();
|
$xml = $generator->generate();
|
||||||
|
|
||||||
// Test that emojis are preserved
|
// Test that emojis are preserved
|
||||||
|
@ -7,12 +7,12 @@ class FeedGeneratorTest extends TestCase
|
|||||||
{
|
{
|
||||||
private function createMockConfig() {
|
private function createMockConfig() {
|
||||||
$mockPdo = $this->createMock(PDO::class);
|
$mockPdo = $this->createMock(PDO::class);
|
||||||
$config = new ConfigModel($mockPdo);
|
$settings = new SettingsModel($mockPdo);
|
||||||
$config->siteTitle = 'Test Site';
|
$settings->siteTitle = 'Test Site';
|
||||||
$config->siteDescription = 'Test Description';
|
$settings->siteDescription = 'Test Description';
|
||||||
$config->baseUrl = 'https://example.com';
|
$settings->baseUrl = 'https://example.com';
|
||||||
$config->basePath = '/tkr/';
|
$settings->basePath = '/tkr/';
|
||||||
return $config;
|
return $settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createSampleTicks() {
|
private function createSampleTicks() {
|
||||||
@ -22,11 +22,11 @@ class FeedGeneratorTest extends TestCase
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createTestGenerator($config = null, $ticks = null) {
|
private function createTestGenerator($settings = null, $ticks = null) {
|
||||||
$config = $config ?? $this->createMockConfig();
|
$settings = $settings ?? $this->createMockConfig();
|
||||||
$ticks = $ticks ?? $this->createSampleTicks();
|
$ticks = $ticks ?? $this->createSampleTicks();
|
||||||
|
|
||||||
return new class($config, $ticks) extends FeedGenerator {
|
return new class($settings, $ticks) extends FeedGenerator {
|
||||||
public function generate(): string {
|
public function generate(): string {
|
||||||
return '<test>content</test>';
|
return '<test>content</test>';
|
||||||
}
|
}
|
||||||
@ -69,12 +69,12 @@ class FeedGeneratorTest extends TestCase
|
|||||||
|
|
||||||
public function testUrlMethodsHandleSubdomainConfiguration() {
|
public function testUrlMethodsHandleSubdomainConfiguration() {
|
||||||
$mockPdo = $this->createMock(PDO::class);
|
$mockPdo = $this->createMock(PDO::class);
|
||||||
$config = new ConfigModel($mockPdo);
|
$settings = new SettingsModel($mockPdo);
|
||||||
$config->siteTitle = 'Test Site';
|
$settings->siteTitle = 'Test Site';
|
||||||
$config->baseUrl = 'https://tkr.example.com';
|
$settings->baseUrl = 'https://tkr.example.com';
|
||||||
$config->basePath = '/';
|
$settings->basePath = '/';
|
||||||
|
|
||||||
$generator = $this->createTestGenerator($config, []);
|
$generator = $this->createTestGenerator($settings, []);
|
||||||
|
|
||||||
$this->assertEquals('https://tkr.example.com/', $generator->testGetSiteUrl());
|
$this->assertEquals('https://tkr.example.com/', $generator->testGetSiteUrl());
|
||||||
$this->assertEquals('https://tkr.example.com/tick/456', $generator->testBuildTickUrl(456));
|
$this->assertEquals('https://tkr.example.com/tick/456', $generator->testBuildTickUrl(456));
|
||||||
@ -82,12 +82,12 @@ class FeedGeneratorTest extends TestCase
|
|||||||
|
|
||||||
public function testUrlMethodsHandleEmptyBasePath() {
|
public function testUrlMethodsHandleEmptyBasePath() {
|
||||||
$mockPdo = $this->createMock(PDO::class);
|
$mockPdo = $this->createMock(PDO::class);
|
||||||
$config = new ConfigModel($mockPdo);
|
$settings = new SettingsModel($mockPdo);
|
||||||
$config->siteTitle = 'Test Site';
|
$settings->siteTitle = 'Test Site';
|
||||||
$config->baseUrl = 'https://example.com';
|
$settings->baseUrl = 'https://example.com';
|
||||||
$config->basePath = '';
|
$settings->basePath = '';
|
||||||
|
|
||||||
$generator = $this->createTestGenerator($config, []);
|
$generator = $this->createTestGenerator($settings, []);
|
||||||
|
|
||||||
$this->assertEquals('https://example.com/', $generator->testGetSiteUrl());
|
$this->assertEquals('https://example.com/', $generator->testGetSiteUrl());
|
||||||
$this->assertEquals('https://example.com/tick/789', $generator->testBuildTickUrl(789));
|
$this->assertEquals('https://example.com/tick/789', $generator->testBuildTickUrl(789));
|
||||||
@ -106,12 +106,12 @@ class FeedGeneratorTest extends TestCase
|
|||||||
|
|
||||||
foreach ($testCases as [$basePath, $expectedSiteUrl, $expectedTickUrl]) {
|
foreach ($testCases as [$basePath, $expectedSiteUrl, $expectedTickUrl]) {
|
||||||
$mockPdo = $this->createMock(PDO::class);
|
$mockPdo = $this->createMock(PDO::class);
|
||||||
$config = new ConfigModel($mockPdo);
|
$settings = new SettingsModel($mockPdo);
|
||||||
$config->siteTitle = 'Test Site';
|
$settings->siteTitle = 'Test Site';
|
||||||
$config->baseUrl = 'https://example.com';
|
$settings->baseUrl = 'https://example.com';
|
||||||
$config->basePath = $basePath;
|
$settings->basePath = $basePath;
|
||||||
|
|
||||||
$generator = $this->createTestGenerator($config, []);
|
$generator = $this->createTestGenerator($settings, []);
|
||||||
|
|
||||||
$this->assertEquals($expectedSiteUrl, $generator->testGetSiteUrl(), "Failed for basePath: '$basePath'");
|
$this->assertEquals($expectedSiteUrl, $generator->testGetSiteUrl(), "Failed for basePath: '$basePath'");
|
||||||
$this->assertEquals($expectedTickUrl, $generator->testBuildTickUrl(123), "Failed for basePath: '$basePath'");
|
$this->assertEquals($expectedTickUrl, $generator->testBuildTickUrl(123), "Failed for basePath: '$basePath'");
|
||||||
|
@ -7,12 +7,12 @@ class RssGeneratorTest extends TestCase
|
|||||||
{
|
{
|
||||||
private function createMockConfig() {
|
private function createMockConfig() {
|
||||||
$mockPdo = $this->createMock(PDO::class);
|
$mockPdo = $this->createMock(PDO::class);
|
||||||
$config = new ConfigModel($mockPdo);
|
$settings = new SettingsModel($mockPdo);
|
||||||
$config->siteTitle = 'Test Site';
|
$settings->siteTitle = 'Test Site';
|
||||||
$config->siteDescription = 'Test Description';
|
$settings->siteDescription = 'Test Description';
|
||||||
$config->baseUrl = 'https://example.com';
|
$settings->baseUrl = 'https://example.com';
|
||||||
$config->basePath = '/tkr/';
|
$settings->basePath = '/tkr/';
|
||||||
return $config;
|
return $settings;
|
||||||
}
|
}
|
||||||
|
|
||||||
private function createSampleTicks() {
|
private function createSampleTicks() {
|
||||||
@ -23,10 +23,10 @@ class RssGeneratorTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testCanGenerateValidRss() {
|
public function testCanGenerateValidRss() {
|
||||||
$config = $this->createMockConfig();
|
$settings = $this->createMockConfig();
|
||||||
$ticks = $this->createSampleTicks();
|
$ticks = $this->createSampleTicks();
|
||||||
|
|
||||||
$generator = new RssGenerator($config, $ticks);
|
$generator = new RssGenerator($settings, $ticks);
|
||||||
$xml = $generator->generate();
|
$xml = $generator->generate();
|
||||||
|
|
||||||
// Test XML structure
|
// Test XML structure
|
||||||
@ -56,8 +56,8 @@ class RssGeneratorTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testCanHandleEmptyTickList() {
|
public function testCanHandleEmptyTickList() {
|
||||||
$config = $this->createMockConfig();
|
$settings = $this->createMockConfig();
|
||||||
$generator = new RssGenerator($config, []);
|
$generator = new RssGenerator($settings, []);
|
||||||
$xml = $generator->generate();
|
$xml = $generator->generate();
|
||||||
|
|
||||||
// Should still be valid RSS with no items
|
// Should still be valid RSS with no items
|
||||||
@ -81,7 +81,7 @@ class RssGeneratorTest extends TestCase
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function testCanHandleSpecialCharactersAndUnicode() {
|
public function testCanHandleSpecialCharactersAndUnicode() {
|
||||||
$config = $this->createMockConfig();
|
$settings = $this->createMockConfig();
|
||||||
|
|
||||||
// Test various challenging characters
|
// Test various challenging characters
|
||||||
$ticks = [
|
$ticks = [
|
||||||
@ -107,7 +107,7 @@ class RssGeneratorTest extends TestCase
|
|||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
$generator = new RssGenerator($config, $ticks);
|
$generator = new RssGenerator($settings, $ticks);
|
||||||
$xml = $generator->generate();
|
$xml = $generator->generate();
|
||||||
|
|
||||||
// Test that emojis are preserved
|
// Test that emojis are preserved
|
||||||
|
@ -13,9 +13,9 @@ class LogTest extends TestCase
|
|||||||
// Create a temporary directory for test logs
|
// Create a temporary directory for test logs
|
||||||
$this->tempLogDir = sys_get_temp_dir() . '/tkr_test_logs_' . uniqid();
|
$this->tempLogDir = sys_get_temp_dir() . '/tkr_test_logs_' . uniqid();
|
||||||
mkdir($this->tempLogDir, 0777, true);
|
mkdir($this->tempLogDir, 0777, true);
|
||||||
|
|
||||||
$this->testLogFile = $this->tempLogDir . '/tkr.log';
|
$this->testLogFile = $this->tempLogDir . '/tkr.log';
|
||||||
|
|
||||||
// Initialize Log with test file and reset route context
|
// Initialize Log with test file and reset route context
|
||||||
Log::init($this->testLogFile);
|
Log::init($this->testLogFile);
|
||||||
Log::setRouteContext('');
|
Log::setRouteContext('');
|
||||||
@ -32,31 +32,31 @@ class LogTest extends TestCase
|
|||||||
private function deleteDirectory(string $dir): void
|
private function deleteDirectory(string $dir): void
|
||||||
{
|
{
|
||||||
if (!is_dir($dir)) return;
|
if (!is_dir($dir)) return;
|
||||||
|
|
||||||
$iterator = new RecursiveIteratorIterator(
|
$iterator = new RecursiveIteratorIterator(
|
||||||
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
|
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
|
||||||
RecursiveIteratorIterator::CHILD_FIRST
|
RecursiveIteratorIterator::CHILD_FIRST
|
||||||
);
|
);
|
||||||
|
|
||||||
foreach ($iterator as $path) {
|
foreach ($iterator as $path) {
|
||||||
$path->isDir() ? rmdir($path->getRealPath()) : unlink($path->getRealPath());
|
$path->isDir() ? rmdir($path->getRealPath()) : unlink($path->getRealPath());
|
||||||
}
|
}
|
||||||
rmdir($dir);
|
rmdir($dir);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function setLogLevel(int $level): void
|
private function setLogLevel(int $level): void
|
||||||
{
|
{
|
||||||
global $app;
|
global $app;
|
||||||
$app = ['config' => (object)['logLevel' => $level]];
|
$app = ['settings' => (object)['logLevel' => $level]];
|
||||||
}
|
}
|
||||||
|
|
||||||
private function assertLogContains(string $message): void
|
private function assertLogContains(string $message): void
|
||||||
{
|
{
|
||||||
$this->assertFileExists($this->testLogFile);
|
$this->assertFileExists($this->testLogFile);
|
||||||
$logContent = file_get_contents($this->testLogFile);
|
$logContent = file_get_contents($this->testLogFile);
|
||||||
$this->assertStringContainsString($message, $logContent);
|
$this->assertStringContainsString($message, $logContent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function assertLogDoesNotContain(string $message): void
|
private function assertLogDoesNotContain(string $message): void
|
||||||
{
|
{
|
||||||
$this->assertFileExists($this->testLogFile);
|
$this->assertFileExists($this->testLogFile);
|
||||||
@ -68,9 +68,9 @@ class LogTest extends TestCase
|
|||||||
{
|
{
|
||||||
Log::setRouteContext('GET /admin');
|
Log::setRouteContext('GET /admin');
|
||||||
$this->setLogLevel(1); // DEBUG level
|
$this->setLogLevel(1); // DEBUG level
|
||||||
|
|
||||||
Log::debug('Test message');
|
Log::debug('Test message');
|
||||||
|
|
||||||
$logContent = file_get_contents($this->testLogFile);
|
$logContent = file_get_contents($this->testLogFile);
|
||||||
$this->assertStringContainsString('[GET /admin]', $logContent);
|
$this->assertStringContainsString('[GET /admin]', $logContent);
|
||||||
$this->assertStringContainsString('Test message', $logContent);
|
$this->assertStringContainsString('Test message', $logContent);
|
||||||
@ -80,11 +80,11 @@ class LogTest extends TestCase
|
|||||||
{
|
{
|
||||||
Log::setRouteContext('');
|
Log::setRouteContext('');
|
||||||
$this->setLogLevel(1);
|
$this->setLogLevel(1);
|
||||||
|
|
||||||
Log::info('Test without route');
|
Log::info('Test without route');
|
||||||
|
|
||||||
$logContent = file_get_contents($this->testLogFile);
|
$logContent = file_get_contents($this->testLogFile);
|
||||||
|
|
||||||
// Should match format without route context: [timestamp] LEVEL: IP - message
|
// Should match format without route context: [timestamp] LEVEL: IP - message
|
||||||
$this->assertMatchesRegularExpression(
|
$this->assertMatchesRegularExpression(
|
||||||
'/\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] INFO: .+ - Test without route/',
|
'/\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] INFO: .+ - Test without route/',
|
||||||
@ -95,12 +95,12 @@ class LogTest extends TestCase
|
|||||||
public function testLogLevelFiltering(): void
|
public function testLogLevelFiltering(): void
|
||||||
{
|
{
|
||||||
$this->setLogLevel(3); // WARNING level
|
$this->setLogLevel(3); // WARNING level
|
||||||
|
|
||||||
Log::debug('Debug message'); // Should be filtered out
|
Log::debug('Debug message'); // Should be filtered out
|
||||||
Log::info('Info message'); // Should be filtered out
|
Log::info('Info message'); // Should be filtered out
|
||||||
Log::warning('Warning message'); // Should be logged
|
Log::warning('Warning message'); // Should be logged
|
||||||
Log::error('Error message'); // Should be logged
|
Log::error('Error message'); // Should be logged
|
||||||
|
|
||||||
$this->assertLogDoesNotContain('Debug message');
|
$this->assertLogDoesNotContain('Debug message');
|
||||||
$this->assertLogDoesNotContain('Info message');
|
$this->assertLogDoesNotContain('Info message');
|
||||||
$this->assertLogContains('Warning message');
|
$this->assertLogContains('Warning message');
|
||||||
@ -111,11 +111,11 @@ class LogTest extends TestCase
|
|||||||
{
|
{
|
||||||
Log::setRouteContext('POST /admin');
|
Log::setRouteContext('POST /admin');
|
||||||
$this->setLogLevel(1);
|
$this->setLogLevel(1);
|
||||||
|
|
||||||
Log::error('Test error message');
|
Log::error('Test error message');
|
||||||
|
|
||||||
$logContent = file_get_contents($this->testLogFile);
|
$logContent = file_get_contents($this->testLogFile);
|
||||||
|
|
||||||
// Check log format: [timestamp] LEVEL: IP [route] - message
|
// Check log format: [timestamp] LEVEL: IP [route] - message
|
||||||
$this->assertMatchesRegularExpression(
|
$this->assertMatchesRegularExpression(
|
||||||
'/\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] ERROR: .+ \[POST \/admin\] - Test error message/',
|
'/\[\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}\] ERROR: .+ \[POST \/admin\] - Test error message/',
|
||||||
@ -126,15 +126,15 @@ class LogTest extends TestCase
|
|||||||
public function testInitCreatesLogDirectory(): void
|
public function testInitCreatesLogDirectory(): void
|
||||||
{
|
{
|
||||||
$newLogFile = $this->tempLogDir . '/nested/logs/test.log';
|
$newLogFile = $this->tempLogDir . '/nested/logs/test.log';
|
||||||
|
|
||||||
// Directory doesn't exist yet
|
// Directory doesn't exist yet
|
||||||
$this->assertDirectoryDoesNotExist(dirname($newLogFile));
|
$this->assertDirectoryDoesNotExist(dirname($newLogFile));
|
||||||
|
|
||||||
Log::init($newLogFile);
|
Log::init($newLogFile);
|
||||||
|
|
||||||
// init() should create the directory
|
// init() should create the directory
|
||||||
$this->assertDirectoryExists(dirname($newLogFile));
|
$this->assertDirectoryExists(dirname($newLogFile));
|
||||||
|
|
||||||
// Verify we can actually write to it
|
// Verify we can actually write to it
|
||||||
$this->setLogLevel(1);
|
$this->setLogLevel(1);
|
||||||
Log::info('Test directory creation');
|
Log::info('Test directory creation');
|
||||||
@ -144,35 +144,35 @@ class LogTest extends TestCase
|
|||||||
public function testLogRotation(): void
|
public function testLogRotation(): void
|
||||||
{
|
{
|
||||||
$this->setLogLevel(1);
|
$this->setLogLevel(1);
|
||||||
|
|
||||||
// Create a log file with exactly 1000 lines (the rotation threshold)
|
// Create a log file with exactly 1000 lines (the rotation threshold)
|
||||||
$logLines = str_repeat("[2025-01-31 12:00:00] INFO: 127.0.0.1 - Test line\n", 1000);
|
$logLines = str_repeat("[2025-01-31 12:00:00] INFO: 127.0.0.1 - Test line\n", 1000);
|
||||||
file_put_contents($this->testLogFile, $logLines);
|
file_put_contents($this->testLogFile, $logLines);
|
||||||
|
|
||||||
// This should trigger rotation
|
// This should trigger rotation
|
||||||
Log::info('This should trigger rotation');
|
Log::info('This should trigger rotation');
|
||||||
|
|
||||||
// Verify rotation happened
|
// Verify rotation happened
|
||||||
$this->assertFileExists($this->testLogFile . '.1');
|
$this->assertFileExists($this->testLogFile . '.1');
|
||||||
$this->assertLogContains('This should trigger rotation');
|
$this->assertLogContains('This should trigger rotation');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testLogRotationLimitsFileCount(): void
|
public function testLogRotationLimitsFileCount(): void
|
||||||
{
|
{
|
||||||
$this->setLogLevel(1);
|
$this->setLogLevel(1);
|
||||||
|
|
||||||
// Create 5 existing rotated log files (.1 through .5)
|
// Create 5 existing rotated log files (.1 through .5)
|
||||||
for ($i = 1; $i <= 5; $i++) {
|
for ($i = 1; $i <= 5; $i++) {
|
||||||
file_put_contents($this->testLogFile . '.' . $i, "Old log file $i\n");
|
file_put_contents($this->testLogFile . '.' . $i, "Old log file $i\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create main log file at rotation threshold
|
// Create main log file at rotation threshold
|
||||||
$logLines = str_repeat("[2025-01-31 12:00:00] INFO: 127.0.0.1 - Test line\n", 1000);
|
$logLines = str_repeat("[2025-01-31 12:00:00] INFO: 127.0.0.1 - Test line\n", 1000);
|
||||||
file_put_contents($this->testLogFile, $logLines);
|
file_put_contents($this->testLogFile, $logLines);
|
||||||
|
|
||||||
// This should trigger rotation and delete the oldest file (.5)
|
// This should trigger rotation and delete the oldest file (.5)
|
||||||
Log::info('Trigger rotation with max files');
|
Log::info('Trigger rotation with max files');
|
||||||
|
|
||||||
// Verify rotation happened and file count is limited
|
// Verify rotation happened and file count is limited
|
||||||
$this->assertFileExists($this->testLogFile . '.1'); // New rotated file
|
$this->assertFileExists($this->testLogFile . '.1'); // New rotated file
|
||||||
$this->assertFileExists($this->testLogFile . '.2'); // Old .1 became .2
|
$this->assertFileExists($this->testLogFile . '.2'); // Old .1 became .2
|
||||||
@ -180,7 +180,7 @@ class LogTest extends TestCase
|
|||||||
$this->assertFileExists($this->testLogFile . '.4'); // Old .3 became .4
|
$this->assertFileExists($this->testLogFile . '.4'); // Old .3 became .4
|
||||||
$this->assertFileExists($this->testLogFile . '.5'); // Old .4 became .5
|
$this->assertFileExists($this->testLogFile . '.5'); // Old .4 became .5
|
||||||
$this->assertFileDoesNotExist($this->testLogFile . '.6'); // Old .5 was deleted
|
$this->assertFileDoesNotExist($this->testLogFile . '.6'); // Old .5 was deleted
|
||||||
|
|
||||||
$this->assertLogContains('Trigger rotation with max files');
|
$this->assertLogContains('Trigger rotation with max files');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,12 +188,12 @@ class LogTest extends TestCase
|
|||||||
{
|
{
|
||||||
// Set up config without logLevel property (simulates missing config value)
|
// Set up config without logLevel property (simulates missing config value)
|
||||||
global $app;
|
global $app;
|
||||||
$app = ['config' => (object)[]];
|
$app = ['settings' => (object)[]];
|
||||||
|
|
||||||
// Should not throw errors and should default to INFO level
|
// Should not throw errors and should default to INFO level
|
||||||
Log::debug('Debug message'); // Should be filtered out (default INFO level = 2)
|
Log::debug('Debug message'); // Should be filtered out (default INFO level = 2)
|
||||||
Log::info('Info message'); // Should be logged
|
Log::info('Info message'); // Should be logged
|
||||||
|
|
||||||
$this->assertLogDoesNotContain('Debug message');
|
$this->assertLogDoesNotContain('Debug message');
|
||||||
$this->assertLogContains('Info message');
|
$this->assertLogContains('Info message');
|
||||||
}
|
}
|
||||||
|
@ -151,7 +151,7 @@ final class UtilTest extends TestCase
|
|||||||
// Set up global $app with config
|
// Set up global $app with config
|
||||||
global $app;
|
global $app;
|
||||||
$app = [
|
$app = [
|
||||||
'config' => (object)['strictAccessibility' => $strictAccessibility]
|
'settings' => (object)['strictAccessibility' => $strictAccessibility]
|
||||||
];
|
];
|
||||||
|
|
||||||
$result = Util::linkify($input);
|
$result = Util::linkify($input);
|
||||||
@ -162,12 +162,12 @@ final class UtilTest extends TestCase
|
|||||||
// Test linkify without new window
|
// Test linkify without new window
|
||||||
global $app;
|
global $app;
|
||||||
$app = [
|
$app = [
|
||||||
'config' => (object)['strictAccessibility' => false]
|
'settings' => (object)['strictAccessibility' => false]
|
||||||
];
|
];
|
||||||
|
|
||||||
$input = 'Visit https://example.com';
|
$input = 'Visit https://example.com';
|
||||||
$expected = 'Visit <a href="https://example.com">https://example.com</a>';
|
$expected = 'Visit <a href="https://example.com">https://example.com</a>';
|
||||||
|
|
||||||
$result = Util::linkify($input, false); // no new window
|
$result = Util::linkify($input, false); // no new window
|
||||||
$this->assertEquals($expected, $result);
|
$this->assertEquals($expected, $result);
|
||||||
}
|
}
|
||||||
@ -176,7 +176,7 @@ final class UtilTest extends TestCase
|
|||||||
// Test basic case with REMOTE_ADDR
|
// Test basic case with REMOTE_ADDR
|
||||||
$_SERVER['REMOTE_ADDR'] = '192.168.1.100';
|
$_SERVER['REMOTE_ADDR'] = '192.168.1.100';
|
||||||
unset($_SERVER['HTTP_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['HTTP_X_REAL_IP']);
|
unset($_SERVER['HTTP_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['HTTP_X_REAL_IP']);
|
||||||
|
|
||||||
$result = Util::getClientIp();
|
$result = Util::getClientIp();
|
||||||
$this->assertEquals('192.168.1.100', $result);
|
$this->assertEquals('192.168.1.100', $result);
|
||||||
}
|
}
|
||||||
@ -187,7 +187,7 @@ final class UtilTest extends TestCase
|
|||||||
$_SERVER['HTTP_X_FORWARDED_FOR'] = '10.0.0.2';
|
$_SERVER['HTTP_X_FORWARDED_FOR'] = '10.0.0.2';
|
||||||
$_SERVER['HTTP_X_REAL_IP'] = '10.0.0.3';
|
$_SERVER['HTTP_X_REAL_IP'] = '10.0.0.3';
|
||||||
$_SERVER['REMOTE_ADDR'] = '10.0.0.4';
|
$_SERVER['REMOTE_ADDR'] = '10.0.0.4';
|
||||||
|
|
||||||
$result = Util::getClientIp();
|
$result = Util::getClientIp();
|
||||||
$this->assertEquals('10.0.0.1', $result); // Should use HTTP_CLIENT_IP
|
$this->assertEquals('10.0.0.1', $result); // Should use HTTP_CLIENT_IP
|
||||||
}
|
}
|
||||||
@ -195,7 +195,7 @@ final class UtilTest extends TestCase
|
|||||||
public function testGetClientIpUnknown(): void {
|
public function testGetClientIpUnknown(): void {
|
||||||
// Test when no IP is available
|
// Test when no IP is available
|
||||||
unset($_SERVER['HTTP_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['HTTP_X_REAL_IP'], $_SERVER['REMOTE_ADDR']);
|
unset($_SERVER['HTTP_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['HTTP_X_REAL_IP'], $_SERVER['REMOTE_ADDR']);
|
||||||
|
|
||||||
$result = Util::getClientIp();
|
$result = Util::getClientIp();
|
||||||
$this->assertEquals('unknown', $result);
|
$this->assertEquals('unknown', $result);
|
||||||
}
|
}
|
||||||
|
@ -175,11 +175,11 @@ try {
|
|||||||
echo "💾 Saving configuration...\n";
|
echo "💾 Saving configuration...\n";
|
||||||
|
|
||||||
// Create/update settings
|
// Create/update settings
|
||||||
$configModel = new ConfigModel($db);
|
$settingsModel = new SettingsModel($db);
|
||||||
$configModel->siteTitle = $siteTitle;
|
$settingsModel->siteTitle = $siteTitle;
|
||||||
$configModel->baseUrl = $baseUrl;
|
$settingsModel->baseUrl = $baseUrl;
|
||||||
$configModel->basePath = $basePath;
|
$settingsModel->basePath = $basePath;
|
||||||
$config = $configModel->save();
|
$settings = $settingsModel->save();
|
||||||
|
|
||||||
// Create admin user
|
// Create admin user
|
||||||
$userModel = new UserModel($db);
|
$userModel = new UserModel($db);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user