Compare commits

...

5 Commits

4 changed files with 156 additions and 74 deletions

View File

@ -0,0 +1,53 @@
<?php
namespace App\Core;
class HookDispatcher
{
/**
* Stores all registered hooks and their callbacks.
* @var array<string, array<callable>>
*/
private static array $hooks = [];
/**
* Register a callback for a given hook.
*/
public static function register(string $hook, callable $callback): void
{
if (!isset(self::$hooks[$hook])) {
self::$hooks[$hook] = [];
}
self::$hooks[$hook][] = $callback;
}
/**
* Dispatch all callbacks for the specified hook.
*/
public static function dispatch(string $hook, array $context = []): void
{
if (!empty(self::$hooks[$hook])) {
foreach (self::$hooks[$hook] as $callback) {
call_user_func($callback, $context);
}
}
}
/**
* Apply filters for a hook key, passing a value through all callbacks.
* Each callback should accept the value and return a modified value.
*
* @param string $hook
* @param mixed $value
* @return mixed
*/
public static function applyFilters(string $hook, $value)
{
if (!empty(self::$hooks[$hook])) {
foreach (self::$hooks[$hook] as $callback) {
$value = call_user_func($callback, $value);
}
}
return $value;
}
}

View File

@ -0,0 +1,37 @@
<?php
namespace App\Core;
class PluginManager
{
/**
* Loads all enabled plugins from the given directory.
*
* @param string $pluginsDir
* @return array<string, array{path: string, meta: array}>
*/
public static function load(string $pluginsDir): array
{
$enabled = [];
foreach (glob($pluginsDir . '*', GLOB_ONLYDIR) as $pluginPath) {
$manifest = $pluginPath . '/plugin.json';
if (!file_exists($manifest)) {
continue;
}
$meta = json_decode(file_get_contents($manifest), true);
if (empty($meta['enabled'])) {
continue;
}
$name = basename($pluginPath);
$enabled[$name] = [
'path' => $pluginPath,
'meta' => $meta,
];
$bootstrap = $pluginPath . '/bootstrap.php';
if (file_exists($bootstrap)) {
include_once $bootstrap;
}
}
return $enabled;
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace App\Core;
use Session;
use Feedback;
class Router {
/**
* Check session validity and handle redirection for protected pages.
* Returns current username if session is valid, null otherwise.
*/
public static function checkAuth(array $config, string $app_root, array $public_pages, string $page): ?string {
$validSession = Session::isValidSession();
if ($validSession) {
return Session::getUsername();
}
if (!in_array($page, $public_pages, true)) {
// flash session timeout if needed
if (isset($_SESSION['LAST_ACTIVITY']) && !isset($_SESSION['session_timeout_shown'])) {
Feedback::flash('LOGIN', 'SESSION_TIMEOUT');
$_SESSION['session_timeout_shown'] = true;
}
// preserve flash messages
$flash_messages = $_SESSION['flash_messages'] ?? [];
Session::cleanup($config);
$_SESSION['flash_messages'] = $flash_messages;
// build login URL
$loginUrl = $app_root . '?page=login';
$trimmed = trim($page, '/?');
if (!in_array($trimmed, INVALID_REDIRECT_PAGES, true)) {
$loginUrl .= '&redirect=' . urlencode($_SERVER['REQUEST_URI']);
}
header('Location: ' . $loginUrl);
exit();
}
return null;
}
}

View File

@ -12,43 +12,31 @@
*/ */
// Preparing plugins and hooks // Preparing plugins and hooks
$GLOBALS['plugin_hooks'] = []; // Initialize HookDispatcher and plugin system
$enabled_plugins = []; require_once __DIR__ . '/../app/core/HookDispatcher.php';
require_once __DIR__ . '/../app/core/PluginManager.php';
use App\Core\HookDispatcher;
use App\Core\PluginManager;
// Plugin discovery // Hook registration and dispatch helpers
function register_hook(string $hook, callable $callback): void {
HookDispatcher::register($hook, $callback);
}
function do_hook(string $hook, array $context = []): void {
HookDispatcher::dispatch($hook, $context);
}
function filter_public_pages(array $pages): array {
return HookDispatcher::applyFilters('filter_public_pages', $pages);
}
function filter_allowed_urls(array $urls): array {
return HookDispatcher::applyFilters('filter_allowed_urls', $urls);
}
// Load enabled plugins
$plugins_dir = dirname(__DIR__) . '/plugins/'; $plugins_dir = dirname(__DIR__) . '/plugins/';
foreach (glob($plugins_dir . '*', GLOB_ONLYDIR) as $plugin_path) { $enabled_plugins = PluginManager::load($plugins_dir);
$manifest = $plugin_path . '/plugin.json';
if (file_exists($manifest)) {
$meta = json_decode(file_get_contents($manifest), true);
if (!empty($meta['enabled'])) {
$plugin_name = basename($plugin_path);
$enabled_plugins[$plugin_name] = [
'path' => $plugin_path,
'meta' => $meta
];
// Autoload plugin bootstrap if exists
$bootstrap = $plugin_path . '/bootstrap.php';
if (file_exists($bootstrap)) {
include_once $bootstrap;
}
}
}
}
$GLOBALS['enabled_plugins'] = $enabled_plugins; $GLOBALS['enabled_plugins'] = $enabled_plugins;
// Simple hook system
function register_hook($hook, $callback) {
$GLOBALS['plugin_hooks'][$hook][] = $callback;
}
function do_hook($hook, $context = []) {
if (!empty($GLOBALS['plugin_hooks'][$hook])) {
foreach ($GLOBALS['plugin_hooks'][$hook] as $callback) {
call_user_func($callback, $context);
}
}
}
// Define CSRF token include path globally // Define CSRF token include path globally
if (!defined('CSRF_TOKEN_INCLUDE')) { if (!defined('CSRF_TOKEN_INCLUDE')) {
define('CSRF_TOKEN_INCLUDE', dirname(__DIR__) . '/app/includes/csrf_token.php'); define('CSRF_TOKEN_INCLUDE', dirname(__DIR__) . '/app/includes/csrf_token.php');
@ -121,14 +109,6 @@ $allowed_urls = [
]; ];
// Let plugins filter/extend allowed_urls // Let plugins filter/extend allowed_urls
function filter_allowed_urls($urls) {
if (!empty($GLOBALS['plugin_hooks']['filter_allowed_urls'])) {
foreach ($GLOBALS['plugin_hooks']['filter_allowed_urls'] as $callback) {
$urls = call_user_func($callback, $urls);
}
}
return $urls;
}
$allowed_urls = filter_allowed_urls($allowed_urls); $allowed_urls = filter_allowed_urls($allowed_urls);
// cnfig file // cnfig file
@ -161,41 +141,11 @@ $app_root = $config['folder'];
$public_pages = ['login', 'help', 'about']; $public_pages = ['login', 'help', 'about'];
// Let plugins filter/extend public_pages // Let plugins filter/extend public_pages
function filter_public_pages($pages) {
if (!empty($GLOBALS['plugin_hooks']['filter_public_pages'])) {
foreach ($GLOBALS['plugin_hooks']['filter_public_pages'] as $callback) {
$pages = call_user_func($callback, $pages);
}
}
return $pages;
}
$public_pages = filter_public_pages($public_pages); $public_pages = filter_public_pages($public_pages);
// Check session and redirect if needed // Dispatch routing and auth
$currentUser = null; require_once __DIR__ . '/../app/core/Router.php';
if ($validSession) { $currentUser = \App\Core\Router::checkAuth($config, $app_root, $public_pages, $page);
// Session is OK
$currentUser = Session::getUsername();
} else if (!in_array($page, $public_pages)) {
// Session expired/invalid, page needs login
if (isset($_SESSION['LAST_ACTIVITY']) && !isset($_SESSION['session_timeout_shown'])) {
// Only show session timeout message if there was an active session
// and we haven't shown it yet
Feedback::flash('LOGIN', 'SESSION_TIMEOUT');
$_SESSION['session_timeout_shown'] = true;
// Cleanup session but keep flash messages
$flash_messages = $_SESSION['flash_messages'] ?? [];
Session::cleanup($config);
$_SESSION['flash_messages'] = $flash_messages;
}
$loginUrl = $app_root . '?page=login';
$trimmed = trim($page, '/?');
if (!in_array($trimmed, INVALID_REDIRECT_PAGES, true)) {
$loginUrl .= '&redirect=' . urlencode($_SERVER['REQUEST_URI']);
}
header('Location: ' . $loginUrl);
exit();
}
// connect to db of Jilo Web // connect to db of Jilo Web
require '../app/classes/database.php'; require '../app/classes/database.php';