simplify-dependency-injection (#44)
Closes https://gitea.subcultureofone.org/greg/tkr/issues/43 Use a global $app dictionary to manage global state rather than having complex class constructors that expect three input arguments. Update and fix tests. Add tests for Util class functions that broke in the refactor. Reviewed-on: https://gitea.subcultureofone.org/greg/tkr/pulls/44 Co-authored-by: Greg Sarjeant <greg@subcultureofone.org> Co-committed-by: Greg Sarjeant <greg@subcultureofone.org>
This commit is contained in:
parent
9593a43cc0
commit
0b0fd29913
@ -41,21 +41,14 @@ if (!(preg_match('/setup$/', $path))) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get a database connection
|
// Initialize application context with all dependencies
|
||||||
// TODO: Change from static function.
|
global $app;
|
||||||
global $db;
|
|
||||||
$db = Database::get();
|
$db = Database::get();
|
||||||
|
$app = [
|
||||||
// Initialize core entities
|
'db' => $db,
|
||||||
// Defining these as globals isn't great practice,
|
'config' => (new ConfigModel($db))->loadFromDatabase(),
|
||||||
// but this is a small, single-user app and this data will rarely change.
|
'user' => (new UserModel($db))->loadFromDatabase(),
|
||||||
global $config;
|
];
|
||||||
global $user;
|
|
||||||
|
|
||||||
$config = new ConfigModel($db);
|
|
||||||
$config = $config->loadFromDatabase();
|
|
||||||
$user = new UserModel($db);
|
|
||||||
$user = $user->loadFromDatabase();
|
|
||||||
|
|
||||||
// Start a session and generate a CSRF Token
|
// Start a session and generate a CSRF Token
|
||||||
// if there isn't already an active session
|
// if there isn't already an active session
|
||||||
@ -63,8 +56,8 @@ Session::start();
|
|||||||
Session::generateCsrfToken();
|
Session::generateCsrfToken();
|
||||||
|
|
||||||
// Remove the base path from the URL
|
// Remove the base path from the URL
|
||||||
if (strpos($path, $config->basePath) === 0) {
|
if (strpos($path, $app['config']->basePath) === 0) {
|
||||||
$path = substr($path, strlen($config->basePath));
|
$path = substr($path, strlen($app['config']->basePath));
|
||||||
}
|
}
|
||||||
|
|
||||||
// strip the trailing slash from the resulting route
|
// strip the trailing slash from the resulting route
|
||||||
@ -99,7 +92,7 @@ if ($method === 'POST' && $path != 'setup') {
|
|||||||
header('Content-Type: text/html; charset=utf-8');
|
header('Content-Type: text/html; charset=utf-8');
|
||||||
|
|
||||||
// Render the requested route or throw a 404
|
// Render the requested route or throw a 404
|
||||||
$router = new Router($db, $config, $user);
|
$router = new Router();
|
||||||
if (!$router->route($path, $method)){
|
if (!$router->route($path, $method)){
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
echo "404 - Page Not Found";
|
echo "404 - Page Not Found";
|
||||||
|
@ -13,22 +13,26 @@ class AdminController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getAdminData(bool $isSetup): array {
|
public function getAdminData(bool $isSetup): array {
|
||||||
|
global $app;
|
||||||
|
|
||||||
Log::debug("Loading admin page" . ($isSetup ? " (setup mode)" : ""));
|
Log::debug("Loading admin page" . ($isSetup ? " (setup mode)" : ""));
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'user' => $this->user,
|
'user' => $app['user'],
|
||||||
'config' => $this->config,
|
'config' => $app['config'],
|
||||||
'isSetup' => $isSetup,
|
'isSetup' => $isSetup,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public function handleSave(){
|
public function handleSave(){
|
||||||
|
global $app;
|
||||||
|
|
||||||
if (!Session::isLoggedIn()){
|
if (!Session::isLoggedIn()){
|
||||||
header('Location: ' . Util::buildRelativeUrl($this->config->basePath, 'login'));
|
header('Location: ' . Util::buildRelativeUrl($app['config']->basePath, 'login'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = $this->processSettingsSave($_POST, false);
|
$result = $this->saveSettings($_POST, false);
|
||||||
header('Location: ' . $_SERVER['PHP_SELF']);
|
header('Location: ' . $_SERVER['PHP_SELF']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
@ -36,12 +40,14 @@ class AdminController extends Controller {
|
|||||||
public function handleSetup(){
|
public function handleSetup(){
|
||||||
// for setup, we don't care if they're logged in
|
// for setup, we don't care if they're logged in
|
||||||
// (because they can't be until setup is complete)
|
// (because they can't be until setup is complete)
|
||||||
$result = $this->processSettingsSave($_POST, true);
|
$result = $this->saveSettings($_POST, true);
|
||||||
header('Location: ' . $_SERVER['PHP_SELF']);
|
header('Location: ' . $_SERVER['PHP_SELF']);
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function processSettingsSave(array $postData, bool $isSetup): array {
|
public function saveSettings(array $postData, bool $isSetup): array {
|
||||||
|
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)" : ""));
|
||||||
@ -122,30 +128,30 @@ class AdminController extends Controller {
|
|||||||
if (empty($errors)) {
|
if (empty($errors)) {
|
||||||
try {
|
try {
|
||||||
// Update site settings
|
// Update site settings
|
||||||
$this->config->siteTitle = $siteTitle;
|
$app['config']->siteTitle = $siteTitle;
|
||||||
$this->config->siteDescription = $siteDescription;
|
$app['config']->siteDescription = $siteDescription;
|
||||||
$this->config->baseUrl = $baseUrl;
|
$app['config']->baseUrl = $baseUrl;
|
||||||
$this->config->basePath = $basePath;
|
$app['config']->basePath = $basePath;
|
||||||
$this->config->itemsPerPage = $itemsPerPage;
|
$app['config']->itemsPerPage = $itemsPerPage;
|
||||||
$this->config->strictAccessibility = $strictAccessibility;
|
$app['config']->strictAccessibility = $strictAccessibility;
|
||||||
$this->config->logLevel = $logLevel;
|
$app['config']->logLevel = $logLevel;
|
||||||
|
|
||||||
// Save site settings and reload config from database
|
// Save site settings and reload config from database
|
||||||
$this->config = $this->config->save();
|
$app['config'] = $app['config']->save();
|
||||||
Log::info("Site settings updated");
|
Log::info("Site settings updated");
|
||||||
|
|
||||||
// Update user profile
|
// Update user profile
|
||||||
$this->user->username = $username;
|
$app['user']->username = $username;
|
||||||
$this->user->displayName = $displayName;
|
$app['user']->displayName = $displayName;
|
||||||
$this->user->website = $website;
|
$app['user']->website = $website;
|
||||||
|
|
||||||
// Save user profile and reload user from database
|
// Save user profile and reload user from database
|
||||||
$this->user = $this->user->save();
|
$app['user'] = $app['user']->save();
|
||||||
Log::info("User profile updated");
|
Log::info("User profile updated");
|
||||||
|
|
||||||
// Update the password if one was sent
|
// Update the password if one was sent
|
||||||
if($password){
|
if($password){
|
||||||
$this->user->setPassword($password);
|
$app['user']->setPassword($password);
|
||||||
Log::info("User password updated");
|
Log::info("User password updated");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,12 @@
|
|||||||
<?php
|
<?php
|
||||||
class AuthController extends Controller {
|
class AuthController extends Controller {
|
||||||
function showLogin(?string $error = null){
|
function showLogin(?string $error = null){
|
||||||
global $config;
|
global $app;
|
||||||
|
|
||||||
$csrf_token = Session::getCsrfToken();
|
$csrf_token = Session::getCsrfToken();
|
||||||
|
|
||||||
$vars = [
|
$vars = [
|
||||||
'config' => $config,
|
'config' => $app['config'],
|
||||||
'csrf_token' => $csrf_token,
|
'csrf_token' => $csrf_token,
|
||||||
'error' => $error,
|
'error' => $error,
|
||||||
];
|
];
|
||||||
@ -14,7 +15,7 @@ class AuthController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleLogin(){
|
function handleLogin(){
|
||||||
global $config;
|
global $app;
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
$username = $_POST['username'] ?? '';
|
$username = $_POST['username'] ?? '';
|
||||||
@ -22,7 +23,7 @@ class AuthController extends Controller {
|
|||||||
|
|
||||||
Log::debug("Login attempt for user {$username}");
|
Log::debug("Login attempt for user {$username}");
|
||||||
|
|
||||||
$userModel = new UserModel();
|
$userModel = new UserModel($app['db']);
|
||||||
$user = $userModel->getByUsername($username);
|
$user = $userModel->getByUsername($username);
|
||||||
|
|
||||||
//if ($user && password_verify($password, $user['password_hash'])) {
|
//if ($user && password_verify($password, $user['password_hash'])) {
|
||||||
@ -30,7 +31,7 @@ class AuthController extends Controller {
|
|||||||
Log::info("Successful login for {$username}");
|
Log::info("Successful login for {$username}");
|
||||||
|
|
||||||
Session::newLoginSession($user);
|
Session::newLoginSession($user);
|
||||||
header('Location: ' . Util::buildRelativeUrl($config->basePath));
|
header('Location: ' . Util::buildRelativeUrl($app['config']->basePath));
|
||||||
exit;
|
exit;
|
||||||
} else {
|
} else {
|
||||||
Log::warning("Failed login for {$username}");
|
Log::warning("Failed login for {$username}");
|
||||||
@ -44,11 +45,12 @@ class AuthController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleLogout(){
|
function handleLogout(){
|
||||||
|
global $app;
|
||||||
|
|
||||||
Log::info("Logout from user " . $_SESSION['username']);
|
Log::info("Logout from user " . $_SESSION['username']);
|
||||||
Session::end();
|
Session::end();
|
||||||
|
|
||||||
global $config;
|
header('Location: ' . Util::buildRelativeUrl($app['config']->basePath));
|
||||||
header('Location: ' . Util::buildRelativeUrl($config->basePath));
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,7 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
class Controller {
|
class Controller {
|
||||||
public function __construct(protected PDO $db, protected ConfigModel $config, protected UserModel $user) {}
|
|
||||||
|
|
||||||
// Renders the requested template inside templates/main/php
|
// Renders the requested template inside templates/main/php
|
||||||
protected function render(string $childTemplateFile, array $vars = []) {
|
protected function render(string $childTemplateFile, array $vars = []) {
|
||||||
$templatePath = TEMPLATES_DIR . "/main.php";
|
$templatePath = TEMPLATES_DIR . "/main.php";
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
|
|
||||||
class CssController extends Controller {
|
class CssController extends Controller {
|
||||||
public function index() {
|
public function index() {
|
||||||
global $config;
|
global $app;
|
||||||
global $user;
|
|
||||||
$customCss = CssModel::load();
|
$customCss = CssModel::load();
|
||||||
|
|
||||||
$vars = [
|
$vars = [
|
||||||
'user' => $user,
|
'user' => $app['user'],
|
||||||
'config' => $config,
|
'config' => $app['config'],
|
||||||
'customCss' => $customCss,
|
'customCss' => $customCss,
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -49,8 +49,6 @@ class CssController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function handlePost() {
|
public function handlePost() {
|
||||||
global $config;
|
|
||||||
|
|
||||||
switch ($_POST['action']) {
|
switch ($_POST['action']) {
|
||||||
case 'upload':
|
case 'upload':
|
||||||
$this->handleUpload();
|
$this->handleUpload();
|
||||||
@ -69,7 +67,7 @@ class CssController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function handleDelete(): void{
|
public function handleDelete(): void{
|
||||||
global $config;
|
global $app;
|
||||||
|
|
||||||
// Don't try to delete the default theme.
|
// Don't try to delete the default theme.
|
||||||
if (!$_POST['selectCssFile']){
|
if (!$_POST['selectCssFile']){
|
||||||
@ -113,26 +111,26 @@ class CssController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Set the theme back to default
|
// Set the theme back to default
|
||||||
$config->cssId = null;
|
$app['config']->cssId = null;
|
||||||
$config = $config->save();
|
$app['config'] = $app['config']->save();
|
||||||
|
|
||||||
// Set flash message
|
// Set flash message
|
||||||
Session::setFlashMessage('success', 'Theme ' . $cssFilename . ' deleted.');
|
Session::setFlashMessage('success', 'Theme ' . $cssFilename . ' deleted.');
|
||||||
}
|
}
|
||||||
|
|
||||||
private function handleSetTheme() {
|
private function handleSetTheme() {
|
||||||
global $config;
|
global $app;
|
||||||
|
|
||||||
if ($_POST['selectCssFile']){
|
if ($_POST['selectCssFile']){
|
||||||
// Set custom theme
|
// Set custom theme
|
||||||
$config->cssId = $_POST['selectCssFile'];
|
$app['config']->cssId = $_POST['selectCssFile'];
|
||||||
} else {
|
} else {
|
||||||
// Set default theme
|
// Set default theme
|
||||||
$config->cssId = null;
|
$app['config']->cssId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the site theme
|
// Update the site theme
|
||||||
$config = $config->save();
|
$app['config'] = $app['config']->save();
|
||||||
|
|
||||||
// Set flash message
|
// Set flash message
|
||||||
Session::setFlashMessage('success', 'Theme applied.');
|
Session::setFlashMessage('success', 'Theme applied.');
|
||||||
|
@ -2,11 +2,12 @@
|
|||||||
class EmojiController extends Controller {
|
class EmojiController extends Controller {
|
||||||
// Shows the custom emoji management page
|
// Shows the custom emoji management page
|
||||||
public function index(){
|
public function index(){
|
||||||
global $config;
|
global $app;
|
||||||
|
|
||||||
$emojiList = EmojiModel::loadAll();
|
$emojiList = EmojiModel::loadAll();
|
||||||
|
|
||||||
$vars = [
|
$vars = [
|
||||||
'config' => $config,
|
'config' => $app['config'],
|
||||||
'emojiList' => $emojiList,
|
'emojiList' => $emojiList,
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -14,7 +15,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function handlePost(): void {
|
public function handlePost(): void {
|
||||||
global $config;
|
global $app;
|
||||||
|
|
||||||
switch ($_POST['action']) {
|
switch ($_POST['action']) {
|
||||||
case 'add':
|
case 'add':
|
||||||
@ -29,7 +30,7 @@
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
header('Location: ' . Util::buildRelativeUrl($config->basePath, 'admin/emoji'));
|
header('Location: ' . Util::buildRelativeUrl($app['config']->basePath, 'admin/emoji'));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,17 +2,19 @@
|
|||||||
class FeedController extends Controller {
|
class FeedController extends Controller {
|
||||||
private $ticks;
|
private $ticks;
|
||||||
|
|
||||||
public function __construct(PDO $db, ConfigModel $config, UserModel $user){
|
public function __construct() {
|
||||||
parent::__construct($db, $config, $user);
|
global $app;
|
||||||
|
|
||||||
$tickModel = new TickModel($db, $config);
|
$tickModel = new TickModel($app['db'], $app['config']);
|
||||||
$this->ticks = $tickModel->getPage($config->itemsPerPage);
|
$this->ticks = $tickModel->getPage($app['config']->itemsPerPage);
|
||||||
|
|
||||||
Log::debug("Loaded " . count($this->ticks) . " ticks for feeds");
|
Log::debug("Loaded " . count($this->ticks) . " ticks for feeds");
|
||||||
}
|
}
|
||||||
|
|
||||||
public function rss(){
|
public function rss(){
|
||||||
$generator = new RssGenerator($this->config, $this->ticks);
|
global $app;
|
||||||
|
|
||||||
|
$generator = new RssGenerator($app['config'], $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());
|
||||||
@ -20,7 +22,9 @@ class FeedController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function atom(){
|
public function atom(){
|
||||||
$generator = new AtomGenerator($this->config, $this->ticks);
|
global $app;
|
||||||
|
|
||||||
|
$generator = new AtomGenerator($app['config'], $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,21 +9,23 @@ class HomeController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getHomeData(int $page): array {
|
public function getHomeData(int $page): array {
|
||||||
|
global $app;
|
||||||
|
|
||||||
Log::debug("Loading home page $page");
|
Log::debug("Loading home page $page");
|
||||||
|
|
||||||
$tickModel = new TickModel($this->db, $this->config);
|
$tickModel = new TickModel($app['db'], $app['config']);
|
||||||
$limit = $this->config->itemsPerPage;
|
$limit = $app['config']->itemsPerPage;
|
||||||
$offset = ($page - 1) * $limit;
|
$offset = ($page - 1) * $limit;
|
||||||
$ticks = $tickModel->getPage($limit, $offset);
|
$ticks = $tickModel->getPage($limit, $offset);
|
||||||
|
|
||||||
$view = new TicksView($this->config, $ticks, $page);
|
$view = new TicksView($app['config'], $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' => $this->config,
|
'config' => $app['config'],
|
||||||
'user' => $this->user,
|
'user' => $app['user'],
|
||||||
'tickList' => $tickList,
|
'tickList' => $tickList,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -31,14 +33,18 @@ class HomeController extends Controller {
|
|||||||
// POST handler
|
// POST handler
|
||||||
// Saves the tick and reloads the homepage
|
// Saves the tick and reloads the homepage
|
||||||
public function handleTick(){
|
public function handleTick(){
|
||||||
|
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($this->config->basePath));
|
header('Location: ' . Util::buildRelativeUrl($app['config']->basePath));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function processTick(array $postData): array {
|
public function processTick(array $postData): array {
|
||||||
|
global $app;
|
||||||
|
|
||||||
$result = ['success' => false, 'message' => ''];
|
$result = ['success' => false, 'message' => ''];
|
||||||
|
|
||||||
if (!isset($postData['new_tick'])) {
|
if (!isset($postData['new_tick'])) {
|
||||||
@ -55,7 +61,7 @@ class HomeController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$tickModel = new TickModel($this->db, $this->config);
|
$tickModel = new TickModel($app['db'], $app['config']);
|
||||||
$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;
|
||||||
|
@ -2,16 +2,16 @@
|
|||||||
class LogController extends Controller {
|
class LogController extends Controller {
|
||||||
private string $storageDir;
|
private string $storageDir;
|
||||||
|
|
||||||
public function __construct(PDO $db, ConfigModel $config, UserModel $user, ?string $storageDir = null) {
|
public function __construct(?string $storageDir = null) {
|
||||||
parent::__construct($db, $config, $user);
|
|
||||||
$this->storageDir = $storageDir ?? STORAGE_DIR;
|
$this->storageDir = $storageDir ?? STORAGE_DIR;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function index() {
|
public function index() {
|
||||||
|
global $app;
|
||||||
|
|
||||||
// Ensure user is logged in
|
// Ensure user is logged in
|
||||||
if (!Session::isLoggedIn()) {
|
if (!Session::isLoggedIn()) {
|
||||||
global $config;
|
header('Location: ' . Util::buildRelativeUrl($app['config']->basePath, 'login'));
|
||||||
header('Location: ' . Util::buildRelativeUrl($config->basePath, 'login'));
|
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ class LogController extends Controller {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function getLogData(string $levelFilter = '', string $routeFilter = ''): array {
|
public function getLogData(string $levelFilter = '', string $routeFilter = ''): array {
|
||||||
global $config;
|
global $app;
|
||||||
|
|
||||||
$limit = 300; // Show last 300 log entries
|
$limit = 300; // Show last 300 log entries
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ class LogController extends Controller {
|
|||||||
$availableLevels = ['DEBUG', 'INFO', 'WARNING', 'ERROR'];
|
$availableLevels = ['DEBUG', 'INFO', 'WARNING', 'ERROR'];
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'config' => $config,
|
'config' => $app['config'],
|
||||||
'logEntries' => $logEntries,
|
'logEntries' => $logEntries,
|
||||||
'availableRoutes' => $availableRoutes,
|
'availableRoutes' => $availableRoutes,
|
||||||
'availableLevels' => $availableLevels,
|
'availableLevels' => $availableLevels,
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
<?php
|
<?php
|
||||||
class MoodController extends Controller {
|
class MoodController extends Controller {
|
||||||
public function index(){
|
public function index(){
|
||||||
global $config;
|
global $app;
|
||||||
global $user;
|
|
||||||
$view = new MoodView();
|
$view = new MoodView();
|
||||||
|
|
||||||
$moodPicker = $view->render_mood_picker(self::getEmojisWithLabels(), $user->mood);
|
$moodPicker = $view->render_mood_picker(self::getEmojisWithLabels(), $app['user']->mood);
|
||||||
|
|
||||||
$vars = [
|
$vars = [
|
||||||
'config' => $config,
|
'config' => $app['config'],
|
||||||
'moodPicker' => $moodPicker,
|
'moodPicker' => $moodPicker,
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -16,11 +16,9 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
public function handlePost(){
|
public function handlePost(){
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
global $app;
|
||||||
// Get the data we need
|
|
||||||
global $config;
|
|
||||||
global $user;
|
|
||||||
|
|
||||||
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
switch ($_POST['action']){
|
switch ($_POST['action']){
|
||||||
case 'set':
|
case 'set':
|
||||||
$mood = $_POST['mood'];
|
$mood = $_POST['mood'];
|
||||||
@ -31,11 +29,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// set or clear the mood
|
// set or clear the mood
|
||||||
$user->mood = $mood;
|
$app['user']->mood = $mood;
|
||||||
$user = $user->save();
|
$app['user'] = $app['user']->save();
|
||||||
|
|
||||||
// go back to the index and show the updated mood
|
// go back to the index and show the updated mood
|
||||||
header('Location: ' . Util::buildRelativeUrl($config->basePath));
|
header('Location: ' . Util::buildRelativeUrl($app['config']->basePath));
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,9 @@
|
|||||||
class TickController extends Controller{
|
class TickController extends Controller{
|
||||||
//public function index(string $year, string $month, string $day, string $hour, string $minute, string $second){
|
//public function index(string $year, string $month, string $day, string $hour, string $minute, string $second){
|
||||||
public function index(int $id){
|
public function index(int $id){
|
||||||
$tickModel = new TickModel();
|
global $app;
|
||||||
|
|
||||||
|
$tickModel = new TickModel($app['db'], $app['config']);
|
||||||
$vars = $tickModel->get($id);
|
$vars = $tickModel->get($id);
|
||||||
$this->render('tick.php', $vars);
|
$this->render('tick.php', $vars);
|
||||||
}
|
}
|
||||||
|
@ -44,8 +44,8 @@ class Log {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static function write($level, $message) {
|
private static function write($level, $message) {
|
||||||
global $config;
|
global $app;
|
||||||
$logLevel = $config->logLevel ?? self::LEVELS['INFO'];
|
$logLevel = $app['config']->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){
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
// Very simple router class
|
// Very simple router class
|
||||||
class Router {
|
class Router {
|
||||||
public function __construct(private PDO $db, private ConfigModel $config, private UserModel $user) {}
|
|
||||||
|
|
||||||
// Define the recognized routes.
|
// Define the recognized routes.
|
||||||
// Anything else will 404.
|
// Anything else will 404.
|
||||||
private static $routeHandlers = [
|
private static $routeHandlers = [
|
||||||
@ -61,7 +59,7 @@ class Router {
|
|||||||
|
|
||||||
Log::debug("Handling request with Controller {$controllerName} and function {$functionName}");
|
Log::debug("Handling request with Controller {$controllerName} and function {$functionName}");
|
||||||
|
|
||||||
$instance = new $controllerName($this->db, $this->config, $this->user);
|
$instance = new $controllerName();
|
||||||
call_user_func_array([$instance, $functionName], $matches);
|
call_user_func_array([$instance, $functionName], $matches);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -25,12 +25,12 @@ class Util {
|
|||||||
return preg_replace_callback(
|
return preg_replace_callback(
|
||||||
'~(https?://[^\s<>"\'()]+)~i',
|
'~(https?://[^\s<>"\'()]+)~i',
|
||||||
function($matches) use ($link_attrs) {
|
function($matches) use ($link_attrs) {
|
||||||
global $config;
|
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 = $config->strictAccessibility ? ' tabindex="0" ' : ' ';
|
$tabIndex = $app['config']->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>';
|
||||||
},
|
},
|
||||||
$text
|
$text
|
||||||
);
|
);
|
||||||
|
@ -16,12 +16,7 @@ class AdminControllerTest extends TestCase
|
|||||||
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');
|
||||||
|
|
||||||
// Set up global config for logging level (DEBUG = 1)
|
// Create mock PDO
|
||||||
global $config;
|
|
||||||
$config = new stdClass();
|
|
||||||
$config->logLevel = 1; // Allow DEBUG level logs
|
|
||||||
|
|
||||||
// Create mock PDO (needed for base constructor)
|
|
||||||
$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
|
||||||
@ -36,6 +31,17 @@ class AdminControllerTest extends TestCase
|
|||||||
$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
|
||||||
|
global $app;
|
||||||
|
$app = [
|
||||||
|
'db' => $this->mockPdo,
|
||||||
|
'config' => $this->config,
|
||||||
|
'user' => $this->user,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Set log level on config for Log class
|
||||||
|
$this->config->logLevel = 1; // Allow DEBUG level logs
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
@ -60,7 +66,7 @@ class AdminControllerTest extends TestCase
|
|||||||
|
|
||||||
public function testGetAdminDataRegularMode(): void
|
public function testGetAdminDataRegularMode(): void
|
||||||
{
|
{
|
||||||
$controller = new AdminController($this->mockPdo, $this->config, $this->user);
|
$controller = new AdminController();
|
||||||
$data = $controller->getAdminData(false);
|
$data = $controller->getAdminData(false);
|
||||||
|
|
||||||
// Should return proper structure
|
// Should return proper structure
|
||||||
@ -76,7 +82,7 @@ class AdminControllerTest extends TestCase
|
|||||||
|
|
||||||
public function testGetAdminDataSetupMode(): void
|
public function testGetAdminDataSetupMode(): void
|
||||||
{
|
{
|
||||||
$controller = new AdminController($this->mockPdo, $this->config, $this->user);
|
$controller = new AdminController();
|
||||||
$data = $controller->getAdminData(true);
|
$data = $controller->getAdminData(true);
|
||||||
|
|
||||||
// Should return proper structure
|
// Should return proper structure
|
||||||
@ -92,8 +98,8 @@ class AdminControllerTest extends TestCase
|
|||||||
|
|
||||||
public function testProcessSettingsSaveWithEmptyData(): void
|
public function testProcessSettingsSaveWithEmptyData(): void
|
||||||
{
|
{
|
||||||
$controller = new AdminController($this->mockPdo, $this->config, $this->user);
|
$controller = new AdminController();
|
||||||
$result = $controller->processSettingsSave([], 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']);
|
||||||
@ -101,7 +107,7 @@ class AdminControllerTest extends TestCase
|
|||||||
|
|
||||||
public function testProcessSettingsSaveValidationErrors(): void
|
public function testProcessSettingsSaveValidationErrors(): void
|
||||||
{
|
{
|
||||||
$controller = new AdminController($this->mockPdo, $this->config, $this->user);
|
$controller = new AdminController();
|
||||||
|
|
||||||
// Test data with multiple validation errors
|
// Test data with multiple validation errors
|
||||||
$postData = [
|
$postData = [
|
||||||
@ -116,7 +122,7 @@ class AdminControllerTest extends TestCase
|
|||||||
'confirm_password' => 'different' // Passwords don't match
|
'confirm_password' => 'different' // Passwords don't match
|
||||||
];
|
];
|
||||||
|
|
||||||
$result = $controller->processSettingsSave($postData, false);
|
$result = $controller->saveSettings($postData, false);
|
||||||
|
|
||||||
$this->assertFalse($result['success']);
|
$this->assertFalse($result['success']);
|
||||||
$this->assertNotEmpty($result['errors']);
|
$this->assertNotEmpty($result['errors']);
|
||||||
@ -157,7 +163,12 @@ class AdminControllerTest extends TestCase
|
|||||||
$config = new ConfigModel($this->mockPdo);
|
$config = new ConfigModel($this->mockPdo);
|
||||||
$user = new UserModel($this->mockPdo);
|
$user = new UserModel($this->mockPdo);
|
||||||
|
|
||||||
$controller = new AdminController($this->mockPdo, $config, $user);
|
// Update global $app with test models
|
||||||
|
global $app;
|
||||||
|
$app['config'] = $config;
|
||||||
|
$app['user'] = $user;
|
||||||
|
|
||||||
|
$controller = new AdminController();
|
||||||
|
|
||||||
$postData = [
|
$postData = [
|
||||||
'username' => 'newuser',
|
'username' => 'newuser',
|
||||||
@ -172,7 +183,7 @@ class AdminControllerTest extends TestCase
|
|||||||
'log_level' => 2
|
'log_level' => 2
|
||||||
];
|
];
|
||||||
|
|
||||||
$result = $controller->processSettingsSave($postData, false);
|
$result = $controller->saveSettings($postData, false);
|
||||||
|
|
||||||
$this->assertTrue($result['success']);
|
$this->assertTrue($result['success']);
|
||||||
$this->assertEmpty($result['errors']);
|
$this->assertEmpty($result['errors']);
|
||||||
@ -214,7 +225,12 @@ class AdminControllerTest extends TestCase
|
|||||||
$config = new ConfigModel($this->mockPdo);
|
$config = new ConfigModel($this->mockPdo);
|
||||||
$user = new UserModel($this->mockPdo);
|
$user = new UserModel($this->mockPdo);
|
||||||
|
|
||||||
$controller = new AdminController($this->mockPdo, $config, $user);
|
// Update global $app with test models
|
||||||
|
global $app;
|
||||||
|
$app['config'] = $config;
|
||||||
|
$app['user'] = $user;
|
||||||
|
|
||||||
|
$controller = new AdminController();
|
||||||
|
|
||||||
$postData = [
|
$postData = [
|
||||||
'username' => 'testuser',
|
'username' => 'testuser',
|
||||||
@ -228,7 +244,7 @@ class AdminControllerTest extends TestCase
|
|||||||
'confirm_password' => 'newpassword'
|
'confirm_password' => 'newpassword'
|
||||||
];
|
];
|
||||||
|
|
||||||
$result = $controller->processSettingsSave($postData, false);
|
$result = $controller->saveSettings($postData, false);
|
||||||
|
|
||||||
$this->assertTrue($result['success']);
|
$this->assertTrue($result['success']);
|
||||||
}
|
}
|
||||||
@ -242,7 +258,12 @@ class AdminControllerTest extends TestCase
|
|||||||
$config = new ConfigModel($this->mockPdo);
|
$config = new ConfigModel($this->mockPdo);
|
||||||
$user = new UserModel($this->mockPdo);
|
$user = new UserModel($this->mockPdo);
|
||||||
|
|
||||||
$controller = new AdminController($this->mockPdo, $config, $user);
|
// Update global $app with test models
|
||||||
|
global $app;
|
||||||
|
$app['config'] = $config;
|
||||||
|
$app['user'] = $user;
|
||||||
|
|
||||||
|
$controller = new AdminController();
|
||||||
|
|
||||||
$postData = [
|
$postData = [
|
||||||
'username' => 'testuser',
|
'username' => 'testuser',
|
||||||
@ -254,7 +275,7 @@ class AdminControllerTest extends TestCase
|
|||||||
'items_per_page' => 10
|
'items_per_page' => 10
|
||||||
];
|
];
|
||||||
|
|
||||||
$result = $controller->processSettingsSave($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']);
|
||||||
@ -262,7 +283,7 @@ class AdminControllerTest extends TestCase
|
|||||||
|
|
||||||
public function testLoggingOnAdminPageLoad(): void
|
public function testLoggingOnAdminPageLoad(): void
|
||||||
{
|
{
|
||||||
$controller = new AdminController($this->mockPdo, $this->config, $this->user);
|
$controller = new AdminController();
|
||||||
$controller->getAdminData(false);
|
$controller->getAdminData(false);
|
||||||
|
|
||||||
// Check that logs were written
|
// Check that logs were written
|
||||||
@ -275,7 +296,7 @@ class AdminControllerTest extends TestCase
|
|||||||
|
|
||||||
public function testLoggingOnSetupPageLoad(): void
|
public function testLoggingOnSetupPageLoad(): void
|
||||||
{
|
{
|
||||||
$controller = new AdminController($this->mockPdo, $this->config, $this->user);
|
$controller = new AdminController();
|
||||||
$controller->getAdminData(true);
|
$controller->getAdminData(true);
|
||||||
|
|
||||||
// Check that logs were written
|
// Check that logs were written
|
||||||
@ -288,7 +309,7 @@ class AdminControllerTest extends TestCase
|
|||||||
|
|
||||||
public function testLoggingOnValidationErrors(): void
|
public function testLoggingOnValidationErrors(): void
|
||||||
{
|
{
|
||||||
$controller = new AdminController($this->mockPdo, $this->config, $this->user);
|
$controller = new AdminController();
|
||||||
|
|
||||||
$postData = [
|
$postData = [
|
||||||
'username' => '', // Will cause validation error
|
'username' => '', // Will cause validation error
|
||||||
@ -299,7 +320,7 @@ class AdminControllerTest extends TestCase
|
|||||||
'items_per_page' => 10
|
'items_per_page' => 10
|
||||||
];
|
];
|
||||||
|
|
||||||
$controller->processSettingsSave($postData, false);
|
$controller->saveSettings($postData, false);
|
||||||
|
|
||||||
// Check that logs were written
|
// Check that logs were written
|
||||||
$logFile = $this->tempLogDir . '/logs/tkr.log';
|
$logFile = $this->tempLogDir . '/logs/tkr.log';
|
||||||
@ -341,7 +362,12 @@ class AdminControllerTest extends TestCase
|
|||||||
$config = new ConfigModel($this->mockPdo);
|
$config = new ConfigModel($this->mockPdo);
|
||||||
$user = new UserModel($this->mockPdo);
|
$user = new UserModel($this->mockPdo);
|
||||||
|
|
||||||
$controller = new AdminController($this->mockPdo, $config, $user);
|
// Update global $app with test models
|
||||||
|
global $app;
|
||||||
|
$app['config'] = $config;
|
||||||
|
$app['user'] = $user;
|
||||||
|
|
||||||
|
$controller = new AdminController();
|
||||||
|
|
||||||
$postData = [
|
$postData = [
|
||||||
'username' => 'testuser',
|
'username' => 'testuser',
|
||||||
@ -353,7 +379,7 @@ class AdminControllerTest extends TestCase
|
|||||||
'items_per_page' => 10
|
'items_per_page' => 10
|
||||||
];
|
];
|
||||||
|
|
||||||
$controller->processSettingsSave($postData, false);
|
$controller->saveSettings($postData, false);
|
||||||
|
|
||||||
// Check that logs were written
|
// Check that logs were written
|
||||||
$logFile = $this->tempLogDir . '/logs/tkr.log';
|
$logFile = $this->tempLogDir . '/logs/tkr.log';
|
||||||
|
@ -16,11 +16,6 @@ class FeedControllerTest extends TestCase
|
|||||||
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');
|
||||||
|
|
||||||
// Set up global config for logging level (DEBUG = 1)
|
|
||||||
global $config;
|
|
||||||
$config = new stdClass();
|
|
||||||
$config->logLevel = 1; // Allow DEBUG level logs
|
|
||||||
|
|
||||||
// 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);
|
||||||
@ -36,6 +31,17 @@ class FeedControllerTest extends TestCase
|
|||||||
// 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
|
||||||
|
global $app;
|
||||||
|
$app = [
|
||||||
|
'db' => $this->mockPdo,
|
||||||
|
'config' => $this->mockConfig,
|
||||||
|
'user' => $this->mockUser,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Set log level on config for Log class
|
||||||
|
$this->mockConfig->logLevel = 1; // Allow DEBUG level logs
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
@ -77,7 +83,7 @@ class FeedControllerTest extends TestCase
|
|||||||
{
|
{
|
||||||
$this->setupMockDatabase([]);
|
$this->setupMockDatabase([]);
|
||||||
|
|
||||||
$controller = new FeedController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
$controller = new FeedController();
|
||||||
|
|
||||||
// Verify it was created successfully
|
// Verify it was created successfully
|
||||||
$this->assertInstanceOf(FeedController::class, $controller);
|
$this->assertInstanceOf(FeedController::class, $controller);
|
||||||
@ -99,7 +105,7 @@ class FeedControllerTest extends TestCase
|
|||||||
|
|
||||||
$this->setupMockDatabase($testTicks);
|
$this->setupMockDatabase($testTicks);
|
||||||
|
|
||||||
$controller = new FeedController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
$controller = new FeedController();
|
||||||
|
|
||||||
// Verify it was created successfully
|
// Verify it was created successfully
|
||||||
$this->assertInstanceOf(FeedController::class, $controller);
|
$this->assertInstanceOf(FeedController::class, $controller);
|
||||||
@ -127,7 +133,7 @@ class FeedControllerTest extends TestCase
|
|||||||
->method('execute')
|
->method('execute')
|
||||||
->with([10, 0]); // itemsPerPage=10, page 1 = offset 0
|
->with([10, 0]); // itemsPerPage=10, page 1 = offset 0
|
||||||
|
|
||||||
new FeedController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
new FeedController();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testRssMethodLogsCorrectly(): void
|
public function testRssMethodLogsCorrectly(): void
|
||||||
@ -138,7 +144,7 @@ class FeedControllerTest extends TestCase
|
|||||||
|
|
||||||
$this->setupMockDatabase($testTicks);
|
$this->setupMockDatabase($testTicks);
|
||||||
|
|
||||||
$controller = new FeedController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
$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();
|
||||||
@ -160,7 +166,7 @@ class FeedControllerTest extends TestCase
|
|||||||
|
|
||||||
$this->setupMockDatabase($testTicks);
|
$this->setupMockDatabase($testTicks);
|
||||||
|
|
||||||
$controller = new FeedController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
$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();
|
||||||
|
@ -16,11 +16,6 @@ class HomeControllerTest extends TestCase
|
|||||||
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');
|
||||||
|
|
||||||
// Set up global config for logging level (DEBUG = 1)
|
|
||||||
global $config;
|
|
||||||
$config = new stdClass();
|
|
||||||
$config->logLevel = 1; // Allow DEBUG level logs
|
|
||||||
|
|
||||||
// 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);
|
||||||
@ -34,6 +29,17 @@ class HomeControllerTest extends TestCase
|
|||||||
$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
|
||||||
|
global $app;
|
||||||
|
$app = [
|
||||||
|
'db' => $this->mockPdo,
|
||||||
|
'config' => $this->mockConfig,
|
||||||
|
'user' => $this->mockUser,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Set log level on config for Log class
|
||||||
|
$this->mockConfig->logLevel = 1; // Allow DEBUG level logs
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
@ -91,7 +97,7 @@ class HomeControllerTest extends TestCase
|
|||||||
{
|
{
|
||||||
$this->setupMockDatabase([]); // Empty array = no ticks
|
$this->setupMockDatabase([]); // Empty array = no ticks
|
||||||
|
|
||||||
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
$controller = new HomeController();
|
||||||
$data = $controller->getHomeData(1);
|
$data = $controller->getHomeData(1);
|
||||||
|
|
||||||
// Should return proper structure
|
// Should return proper structure
|
||||||
@ -118,7 +124,7 @@ class HomeControllerTest extends TestCase
|
|||||||
|
|
||||||
$this->setupMockDatabase($testTicks);
|
$this->setupMockDatabase($testTicks);
|
||||||
|
|
||||||
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
$controller = new HomeController();
|
||||||
$data = $controller->getHomeData(1);
|
$data = $controller->getHomeData(1);
|
||||||
|
|
||||||
// Should return proper structure
|
// Should return proper structure
|
||||||
@ -147,7 +153,7 @@ class HomeControllerTest extends TestCase
|
|||||||
->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($this->mockPdo, $this->mockConfig, $this->mockUser);
|
$controller = new HomeController();
|
||||||
$controller->getHomeData(2); // Page 2
|
$controller->getHomeData(2); // Page 2
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -171,7 +177,7 @@ class HomeControllerTest extends TestCase
|
|||||||
&& $params[1] === 'This is a test tick';
|
&& $params[1] === 'This is a test tick';
|
||||||
}));
|
}));
|
||||||
|
|
||||||
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
$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);
|
||||||
@ -185,7 +191,7 @@ 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($this->mockPdo, $this->mockConfig, $this->mockUser);
|
$controller = new HomeController();
|
||||||
$postData = ['new_tick' => ' ']; // Just whitespace
|
$postData = ['new_tick' => ' ']; // Just whitespace
|
||||||
|
|
||||||
$result = $controller->processTick($postData);
|
$result = $controller->processTick($postData);
|
||||||
@ -199,7 +205,7 @@ 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($this->mockPdo, $this->mockConfig, $this->mockUser);
|
$controller = new HomeController();
|
||||||
$postData = []; // No new_tick field
|
$postData = []; // No new_tick field
|
||||||
|
|
||||||
$result = $controller->processTick($postData);
|
$result = $controller->processTick($postData);
|
||||||
@ -219,7 +225,7 @@ class HomeControllerTest extends TestCase
|
|||||||
return $params[1] === 'This has whitespace'; // Should be trimmed
|
return $params[1] === 'This has whitespace'; // Should be trimmed
|
||||||
}));
|
}));
|
||||||
|
|
||||||
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
$controller = new HomeController();
|
||||||
$postData = ['new_tick' => ' This has whitespace '];
|
$postData = ['new_tick' => ' This has whitespace '];
|
||||||
|
|
||||||
$result = $controller->processTick($postData);
|
$result = $controller->processTick($postData);
|
||||||
@ -231,7 +237,7 @@ class HomeControllerTest extends TestCase
|
|||||||
{
|
{
|
||||||
$this->setupMockDatabaseForInsert(false); // Will throw exception
|
$this->setupMockDatabaseForInsert(false); // Will throw exception
|
||||||
|
|
||||||
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
$controller = new HomeController();
|
||||||
$postData = ['new_tick' => 'This will fail'];
|
$postData = ['new_tick' => 'This will fail'];
|
||||||
|
|
||||||
$result = $controller->processTick($postData);
|
$result = $controller->processTick($postData);
|
||||||
@ -247,7 +253,7 @@ class HomeControllerTest extends TestCase
|
|||||||
];
|
];
|
||||||
$this->setupMockDatabase($testTicks);
|
$this->setupMockDatabase($testTicks);
|
||||||
|
|
||||||
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
$controller = new HomeController();
|
||||||
$controller->getHomeData(1);
|
$controller->getHomeData(1);
|
||||||
|
|
||||||
// Check that logs were written
|
// Check that logs were written
|
||||||
@ -263,7 +269,7 @@ class HomeControllerTest extends TestCase
|
|||||||
{
|
{
|
||||||
$this->setupMockDatabaseForInsert(true);
|
$this->setupMockDatabaseForInsert(true);
|
||||||
|
|
||||||
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
$controller = new HomeController();
|
||||||
$postData = ['new_tick' => 'Test tick for logging'];
|
$postData = ['new_tick' => 'Test tick for logging'];
|
||||||
|
|
||||||
$controller->processTick($postData);
|
$controller->processTick($postData);
|
||||||
@ -278,7 +284,7 @@ class HomeControllerTest extends TestCase
|
|||||||
|
|
||||||
public function testLoggingOnEmptyTick(): void
|
public function testLoggingOnEmptyTick(): void
|
||||||
{
|
{
|
||||||
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
$controller = new HomeController();
|
||||||
$postData = ['new_tick' => ''];
|
$postData = ['new_tick' => ''];
|
||||||
|
|
||||||
$controller->processTick($postData);
|
$controller->processTick($postData);
|
||||||
@ -297,7 +303,7 @@ class HomeControllerTest extends TestCase
|
|||||||
{
|
{
|
||||||
$this->setupMockDatabaseForInsert(false);
|
$this->setupMockDatabaseForInsert(false);
|
||||||
|
|
||||||
$controller = new HomeController($this->mockPdo, $this->mockConfig, $this->mockUser);
|
$controller = new HomeController();
|
||||||
$postData = ['new_tick' => 'This will fail'];
|
$postData = ['new_tick' => 'This will fail'];
|
||||||
|
|
||||||
$controller->processTick($postData);
|
$controller->processTick($postData);
|
||||||
|
@ -19,12 +19,20 @@ class LogControllerTest extends TestCase
|
|||||||
$this->originalGet = $_GET;
|
$this->originalGet = $_GET;
|
||||||
$_GET = [];
|
$_GET = [];
|
||||||
|
|
||||||
// Mock global config
|
// Set up global $app for simplified dependency access
|
||||||
global $config;
|
|
||||||
$mockPdo = $this->createMock(PDO::class);
|
$mockPdo = $this->createMock(PDO::class);
|
||||||
$config = new ConfigModel($mockPdo);
|
$mockConfig = new ConfigModel($mockPdo);
|
||||||
$config->baseUrl = 'https://example.com';
|
$mockConfig->baseUrl = 'https://example.com';
|
||||||
$config->basePath = '/tkr/';
|
$mockConfig->basePath = '/tkr/';
|
||||||
|
|
||||||
|
$mockUser = new UserModel($mockPdo);
|
||||||
|
|
||||||
|
global $app;
|
||||||
|
$app = [
|
||||||
|
'db' => $mockPdo,
|
||||||
|
'config' => $mockConfig,
|
||||||
|
'user' => $mockUser,
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function tearDown(): void
|
protected function tearDown(): void
|
||||||
@ -51,10 +59,8 @@ class LogControllerTest extends TestCase
|
|||||||
|
|
||||||
public function testGetLogDataWithNoLogFiles(): void
|
public function testGetLogDataWithNoLogFiles(): void
|
||||||
{
|
{
|
||||||
$mockPdo = $this->createMock(PDO::class);
|
// Uses global $app set up in setUp()
|
||||||
$mockConfig = new ConfigModel($mockPdo);
|
$controller = new LogController($this->tempLogDir);
|
||||||
$mockUser = new UserModel($mockPdo);
|
|
||||||
$controller = new LogController($mockPdo, $mockConfig, $mockUser, $this->tempLogDir);
|
|
||||||
$data = $controller->getLogData();
|
$data = $controller->getLogData();
|
||||||
|
|
||||||
// Should return empty log entries but valid structure
|
// Should return empty log entries but valid structure
|
||||||
@ -85,10 +91,8 @@ class LogControllerTest extends TestCase
|
|||||||
|
|
||||||
file_put_contents($this->testLogFile, $logContent);
|
file_put_contents($this->testLogFile, $logContent);
|
||||||
|
|
||||||
$mockPdo = $this->createMock(PDO::class);
|
// Uses global $app set up in setUp()
|
||||||
$mockConfig = new ConfigModel($mockPdo);
|
$controller = new LogController($this->tempLogDir);
|
||||||
$mockUser = new UserModel($mockPdo);
|
|
||||||
$controller = new LogController($mockPdo, $mockConfig, $mockUser, $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
|
||||||
@ -129,10 +133,8 @@ class LogControllerTest extends TestCase
|
|||||||
|
|
||||||
file_put_contents($this->testLogFile, $logContent);
|
file_put_contents($this->testLogFile, $logContent);
|
||||||
|
|
||||||
$mockPdo = $this->createMock(PDO::class);
|
// Uses global $app set up in setUp()
|
||||||
$mockConfig = new ConfigModel($mockPdo);
|
$controller = new LogController($this->tempLogDir);
|
||||||
$mockUser = new UserModel($mockPdo);
|
|
||||||
$controller = new LogController($mockPdo, $mockConfig, $mockUser, $this->tempLogDir);
|
|
||||||
$data = $controller->getLogData('ERROR');
|
$data = $controller->getLogData('ERROR');
|
||||||
|
|
||||||
// Should only include ERROR entries
|
// Should only include ERROR entries
|
||||||
@ -152,10 +154,8 @@ class LogControllerTest extends TestCase
|
|||||||
|
|
||||||
file_put_contents($this->testLogFile, $logContent);
|
file_put_contents($this->testLogFile, $logContent);
|
||||||
|
|
||||||
$mockPdo = $this->createMock(PDO::class);
|
// Uses global $app set up in setUp()
|
||||||
$mockConfig = new ConfigModel($mockPdo);
|
$controller = new LogController($this->tempLogDir);
|
||||||
$mockUser = new UserModel($mockPdo);
|
|
||||||
$controller = new LogController($mockPdo, $mockConfig, $mockUser, $this->tempLogDir);
|
|
||||||
$data = $controller->getLogData('', 'GET /admin');
|
$data = $controller->getLogData('', 'GET /admin');
|
||||||
|
|
||||||
// Should only include GET /admin entries
|
// Should only include GET /admin entries
|
||||||
@ -175,10 +175,8 @@ class LogControllerTest extends TestCase
|
|||||||
|
|
||||||
file_put_contents($this->testLogFile, $logContent);
|
file_put_contents($this->testLogFile, $logContent);
|
||||||
|
|
||||||
$mockPdo = $this->createMock(PDO::class);
|
// Uses global $app set up in setUp()
|
||||||
$mockConfig = new ConfigModel($mockPdo);
|
$controller = new LogController($this->tempLogDir);
|
||||||
$mockUser = new UserModel($mockPdo);
|
|
||||||
$controller = new LogController($mockPdo, $mockConfig, $mockUser, $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
|
||||||
@ -201,10 +199,8 @@ class LogControllerTest extends TestCase
|
|||||||
$rotatedLog2 = '[2025-01-31 12:00:00] WARNING: 127.0.0.1 - Rotated log entry 2';
|
$rotatedLog2 = '[2025-01-31 12:00:00] WARNING: 127.0.0.1 - Rotated log entry 2';
|
||||||
file_put_contents($this->testLogFile . '.2', $rotatedLog2);
|
file_put_contents($this->testLogFile . '.2', $rotatedLog2);
|
||||||
|
|
||||||
$mockPdo = $this->createMock(PDO::class);
|
// Uses global $app set up in setUp()
|
||||||
$mockConfig = new ConfigModel($mockPdo);
|
$controller = new LogController($this->tempLogDir);
|
||||||
$mockUser = new UserModel($mockPdo);
|
|
||||||
$controller = new LogController($mockPdo, $mockConfig, $mockUser, $this->tempLogDir);
|
|
||||||
$data = $controller->getLogData();
|
$data = $controller->getLogData();
|
||||||
|
|
||||||
// Should read from all log files, newest first
|
// Should read from all log files, newest first
|
||||||
@ -226,10 +222,8 @@ class LogControllerTest extends TestCase
|
|||||||
|
|
||||||
file_put_contents($this->testLogFile, $logContent);
|
file_put_contents($this->testLogFile, $logContent);
|
||||||
|
|
||||||
$mockPdo = $this->createMock(PDO::class);
|
// Uses global $app set up in setUp()
|
||||||
$mockConfig = new ConfigModel($mockPdo);
|
$controller = new LogController($this->tempLogDir);
|
||||||
$mockUser = new UserModel($mockPdo);
|
|
||||||
$controller = new LogController($mockPdo, $mockConfig, $mockUser, $this->tempLogDir);
|
|
||||||
$data = $controller->getLogData();
|
$data = $controller->getLogData();
|
||||||
|
|
||||||
// Should extract unique routes, sorted
|
// Should extract unique routes, sorted
|
||||||
@ -248,10 +242,8 @@ class LogControllerTest extends TestCase
|
|||||||
|
|
||||||
file_put_contents($this->testLogFile, $logContent);
|
file_put_contents($this->testLogFile, $logContent);
|
||||||
|
|
||||||
$mockPdo = $this->createMock(PDO::class);
|
// Uses global $app set up in setUp()
|
||||||
$mockConfig = new ConfigModel($mockPdo);
|
$controller = new LogController($this->tempLogDir);
|
||||||
$mockUser = new UserModel($mockPdo);
|
|
||||||
$controller = new LogController($mockPdo, $mockConfig, $mockUser, $this->tempLogDir);
|
|
||||||
$data = $controller->getLogData();
|
$data = $controller->getLogData();
|
||||||
|
|
||||||
// Should only include valid entries, ignore invalid ones
|
// Should only include valid entries, ignore invalid ones
|
||||||
|
@ -43,10 +43,11 @@ class LogTest extends TestCase
|
|||||||
{
|
{
|
||||||
Log::setRouteContext('GET /admin');
|
Log::setRouteContext('GET /admin');
|
||||||
|
|
||||||
// Create a mock config for log level
|
// Create a mock app config for log level
|
||||||
global $config;
|
global $app;
|
||||||
$config = new stdClass();
|
$app = [
|
||||||
$config->logLevel = 1; // DEBUG level
|
'config' => (object)['logLevel' => 1] // DEBUG level
|
||||||
|
];
|
||||||
|
|
||||||
Log::debug('Test message');
|
Log::debug('Test message');
|
||||||
|
|
||||||
@ -61,9 +62,10 @@ class LogTest extends TestCase
|
|||||||
{
|
{
|
||||||
Log::setRouteContext('');
|
Log::setRouteContext('');
|
||||||
|
|
||||||
global $config;
|
global $app;
|
||||||
$config = new stdClass();
|
$app = [
|
||||||
$config->logLevel = 1;
|
'config' => (object)['logLevel' => 1]
|
||||||
|
];
|
||||||
|
|
||||||
Log::info('Test without route');
|
Log::info('Test without route');
|
||||||
|
|
||||||
@ -78,9 +80,10 @@ class LogTest extends TestCase
|
|||||||
|
|
||||||
public function testLogLevelFiltering(): void
|
public function testLogLevelFiltering(): void
|
||||||
{
|
{
|
||||||
global $config;
|
global $app;
|
||||||
$config = new stdClass();
|
$app = [
|
||||||
$config->logLevel = 3; // WARNING level
|
'config' => (object)['logLevel' => 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
|
||||||
@ -99,9 +102,10 @@ class LogTest extends TestCase
|
|||||||
{
|
{
|
||||||
Log::setRouteContext('POST /admin');
|
Log::setRouteContext('POST /admin');
|
||||||
|
|
||||||
global $config;
|
global $app;
|
||||||
$config = new stdClass();
|
$app = [
|
||||||
$config->logLevel = 1;
|
'config' => (object)['logLevel' => 1]
|
||||||
|
];
|
||||||
|
|
||||||
Log::error('Test error message');
|
Log::error('Test error message');
|
||||||
|
|
||||||
@ -129,9 +133,10 @@ class LogTest extends TestCase
|
|||||||
|
|
||||||
public function testLogRotation(): void
|
public function testLogRotation(): void
|
||||||
{
|
{
|
||||||
global $config;
|
global $app;
|
||||||
$config = new stdClass();
|
$app = [
|
||||||
$config->logLevel = 1;
|
'config' => (object)['logLevel' => 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);
|
||||||
@ -154,9 +159,11 @@ class LogTest extends TestCase
|
|||||||
|
|
||||||
public function testDefaultLogLevelWhenConfigMissing(): void
|
public function testDefaultLogLevelWhenConfigMissing(): void
|
||||||
{
|
{
|
||||||
// Clear global config
|
// Set up config without logLevel property (simulates missing config value)
|
||||||
global $config;
|
global $app;
|
||||||
$config = null;
|
$app = [
|
||||||
|
'config' => (object)[] // Empty config object, no logLevel property
|
||||||
|
];
|
||||||
|
|
||||||
// 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)
|
||||||
|
@ -73,4 +73,134 @@ final class UtilTest extends TestCase
|
|||||||
$this->assertEquals($expected, $result);
|
$this->assertEquals($expected, $result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test data for escape_html function
|
||||||
|
public static function escapeHtmlProvider(): array {
|
||||||
|
return [
|
||||||
|
'basic HTML' => ['<script>alert("xss")</script>', '<script>alert("xss")</script>'],
|
||||||
|
'quotes' => ['He said "Hello" & she said \'Hi\'', 'He said "Hello" & she said 'Hi''],
|
||||||
|
'empty string' => ['', ''],
|
||||||
|
'normal text' => ['Hello World', 'Hello World'],
|
||||||
|
'ampersand' => ['Tom & Jerry', 'Tom & Jerry'],
|
||||||
|
'unicode' => ['🚀 emoji & text', '🚀 emoji & text'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('escapeHtmlProvider')]
|
||||||
|
public function testEscapeHtml(string $input, string $expected): void {
|
||||||
|
$result = Util::escape_html($input);
|
||||||
|
$this->assertEquals($expected, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test data for escape_xml function
|
||||||
|
public static function escapeXmlProvider(): array {
|
||||||
|
return [
|
||||||
|
'basic XML' => ['<tag attr="value">content</tag>', '<tag attr="value">content</tag>'],
|
||||||
|
'quotes and ampersand' => ['Title & "Subtitle"', 'Title & "Subtitle"'],
|
||||||
|
'empty string' => ['', ''],
|
||||||
|
'normal text' => ['Hello World', 'Hello World'],
|
||||||
|
'unicode' => ['🎵 music & notes', '🎵 music & notes'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('escapeXmlProvider')]
|
||||||
|
public function testEscapeXml(string $input, string $expected): void {
|
||||||
|
$result = Util::escape_xml($input);
|
||||||
|
$this->assertEquals($expected, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test data for linkify function
|
||||||
|
public static function linkifyProvider(): array {
|
||||||
|
return [
|
||||||
|
'simple URL' => [
|
||||||
|
'Check out https://example.com for more info',
|
||||||
|
'Check out <a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a> for more info',
|
||||||
|
false // not strict accessibility
|
||||||
|
],
|
||||||
|
'URL with path' => [
|
||||||
|
'Visit https://example.com/path/to/page',
|
||||||
|
'Visit <a href="https://example.com/path/to/page" target="_blank" rel="noopener noreferrer">https://example.com/path/to/page</a>',
|
||||||
|
false
|
||||||
|
],
|
||||||
|
'multiple URLs' => [
|
||||||
|
'See https://example.com and https://other.com',
|
||||||
|
'See <a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a> and <a href="https://other.com" target="_blank" rel="noopener noreferrer">https://other.com</a>',
|
||||||
|
false
|
||||||
|
],
|
||||||
|
'URL with punctuation' => [
|
||||||
|
'Check https://example.com.',
|
||||||
|
'Check <a href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a>',
|
||||||
|
false
|
||||||
|
],
|
||||||
|
'no URL' => [
|
||||||
|
'Just some regular text',
|
||||||
|
'Just some regular text',
|
||||||
|
false
|
||||||
|
],
|
||||||
|
'strict accessibility mode' => [
|
||||||
|
'Visit https://example.com now',
|
||||||
|
'Visit <a tabindex="0" href="https://example.com" target="_blank" rel="noopener noreferrer">https://example.com</a> now',
|
||||||
|
true // strict accessibility
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
#[DataProvider('linkifyProvider')]
|
||||||
|
public function testLinkify(string $input, string $expected, bool $strictAccessibility): void {
|
||||||
|
// Set up global $app with config
|
||||||
|
global $app;
|
||||||
|
$app = [
|
||||||
|
'config' => (object)['strictAccessibility' => $strictAccessibility]
|
||||||
|
];
|
||||||
|
|
||||||
|
$result = Util::linkify($input);
|
||||||
|
$this->assertEquals($expected, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testLinkifyNoNewWindow(): void {
|
||||||
|
// Test linkify without new window
|
||||||
|
global $app;
|
||||||
|
$app = [
|
||||||
|
'config' => (object)['strictAccessibility' => false]
|
||||||
|
];
|
||||||
|
|
||||||
|
$input = 'Visit https://example.com';
|
||||||
|
$expected = 'Visit <a href="https://example.com">https://example.com</a>';
|
||||||
|
|
||||||
|
$result = Util::linkify($input, false); // no new window
|
||||||
|
$this->assertEquals($expected, $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetClientIp(): void {
|
||||||
|
// Test basic case with REMOTE_ADDR
|
||||||
|
$_SERVER['REMOTE_ADDR'] = '192.168.1.100';
|
||||||
|
unset($_SERVER['HTTP_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['HTTP_X_REAL_IP']);
|
||||||
|
|
||||||
|
$result = Util::getClientIp();
|
||||||
|
$this->assertEquals('192.168.1.100', $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetClientIpWithForwardedHeaders(): void {
|
||||||
|
// Test precedence: HTTP_CLIENT_IP > HTTP_X_FORWARDED_FOR > HTTP_X_REAL_IP > REMOTE_ADDR
|
||||||
|
$_SERVER['HTTP_CLIENT_IP'] = '10.0.0.1';
|
||||||
|
$_SERVER['HTTP_X_FORWARDED_FOR'] = '10.0.0.2';
|
||||||
|
$_SERVER['HTTP_X_REAL_IP'] = '10.0.0.3';
|
||||||
|
$_SERVER['REMOTE_ADDR'] = '10.0.0.4';
|
||||||
|
|
||||||
|
$result = Util::getClientIp();
|
||||||
|
$this->assertEquals('10.0.0.1', $result); // Should use HTTP_CLIENT_IP
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetClientIpUnknown(): void {
|
||||||
|
// Test when no IP is available
|
||||||
|
unset($_SERVER['HTTP_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['HTTP_X_REAL_IP'], $_SERVER['REMOTE_ADDR']);
|
||||||
|
|
||||||
|
$result = Util::getClientIp();
|
||||||
|
$this->assertEquals('unknown', $result);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function tearDown(): void {
|
||||||
|
// Clean up $_SERVER after IP tests
|
||||||
|
unset($_SERVER['HTTP_CLIENT_IP'], $_SERVER['HTTP_X_FORWARDED_FOR'], $_SERVER['HTTP_X_REAL_IP'], $_SERVER['REMOTE_ADDR']);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
Loading…
x
Reference in New Issue
Block a user