diff --git a/.gitignore b/.gitignore index 245d6fa4..12888e08 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ node_modules yarn-error.log *.swp includes/config.php +plugins/ rootCA.pem vendor .env diff --git a/includes/functions.php b/includes/functions.php index cc5d6c99..b50bafb4 100755 --- a/includes/functions.php +++ b/includes/functions.php @@ -542,9 +542,13 @@ function ConvertToSecurity($security) /** * Renders a simple PHP template */ -function renderTemplate($name, $__template_data = []) +function renderTemplate($name, $__template_data = [], $pluginName = null) { - $file = realpath(dirname(__FILE__) . "/../templates/$name.php"); + if (is_string($pluginName)) { + $file = realpath(dirname(__FILE__) . "/../plugins/$pluginName/templates/$name.php"); + } else { + $file = realpath(dirname(__FILE__) . "/../templates/$name.php"); + } if (!file_exists($file)) { return "template $name ($file) not found"; } @@ -1005,3 +1009,27 @@ function lightenColor($color, $percent) return sprintf("#%02x%02x%02x", $r, $g, $b); } + +function renderStatus($hostapd_led, $hostapd_status, $memused_led, $memused, $cputemp_led, $cputemp) +{ + ?> +
+ +
+
Status
+
+ +
+
+ % +
+
+ °C +
+
+
+ handlePageAction($page)) { + // If no plugin is available fall back to core page action handlers + handleCorePageAction($page, $extraFooterScripts); +} + +/** + * Core application page handling + * + * @param string $page + * @param array $extraFooterScripts + * @return void + */ +function handleCorePageAction(string $page, array &$extraFooterScripts): void +{ + switch ($page) { case "/wlan0_info": DisplayDashboard($extraFooterScripts); break; @@ -53,6 +72,6 @@ break; default: DisplayDashboard($extraFooterScripts); - } - ?> + } +} diff --git a/includes/sidebar.php b/includes/sidebar.php index ba0566c1..f66f1af4 100755 --- a/includes/sidebar.php +++ b/includes/sidebar.php @@ -1,89 +1,15 @@ -
- -
-
Status
-
- -
-
- % -
-
- °C -
-
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +getSidebar(); +$sidebar->render(); diff --git a/includes/torproxy.php b/includes/torproxy.php deleted file mode 100755 index d6166765..00000000 --- a/includes/torproxy.php +++ /dev/null @@ -1,50 +0,0 @@ -addMessage($line, 'info'); - } - } elseif (isset($_POST['StopTOR'])) { - echo "Attempting to stop TOR"; - exec('sudo systemctl stop tor.service', $return); - foreach ($return as $line) { - $status->addMessage($line, 'info'); - } - } -} -?> diff --git a/index.php b/index.php index af279cb8..f8913ae5 100755 --- a/index.php +++ b/index.php @@ -32,6 +32,8 @@ require_once 'includes/autoload.php'; require_once 'includes/defaults.php'; require_once 'includes/locale.php'; require_once 'includes/functions.php'; + +// Default page actions require_once 'includes/dashboard.php'; require_once 'includes/authenticate.php'; require_once 'includes/admin.php'; @@ -48,7 +50,6 @@ require_once 'includes/openvpn.php'; require_once 'includes/wireguard.php'; require_once 'includes/provider.php'; require_once 'includes/restapi.php'; -require_once 'includes/torproxy.php'; initializeApp(); ?> diff --git a/src/RaspAP/Plugins/PluginInterface.php b/src/RaspAP/Plugins/PluginInterface.php new file mode 100644 index 00000000..2fb21921 --- /dev/null +++ b/src/RaspAP/Plugins/PluginInterface.php @@ -0,0 +1,34 @@ + + * @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE + * @see + */ + +declare(strict_types=1); + +namespace RaspAP\Plugins; + +use RaspAP\UI\Sidebar; + +interface PluginInterface +{ + /** + * Initialize the plugin + * @param Sidebar $sidebar Sidebar instance for adding items + */ + public function initialize(Sidebar $sidebar): void; + + /** + * Render a template within the plugin's template directory + * @param string $templateName + * @param array $__data + * @return string + */ + public function renderTemplate(string $templateName, array $__data = []): string; +} + diff --git a/src/RaspAP/Plugins/PluginManager.php b/src/RaspAP/Plugins/PluginManager.php new file mode 100644 index 00000000..d1de40d5 --- /dev/null +++ b/src/RaspAP/Plugins/PluginManager.php @@ -0,0 +1,117 @@ + + * Special thanks to GitHub user @assachs + * @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE + */ + +declare(strict_types=1); + +namespace RaspAP\Plugins; + +use RaspAP\UI\Sidebar; + +class PluginManager +{ + private static $instance = null; + private $plugins = []; + private $sidebar; + + private function __construct() + { + $this->pluginPath = 'plugins'; + $this->sidebar = new Sidebar(); + $this->autoloadPlugins(); // autoload plugins on instantiation + } + + // Get the single instance of PluginManager + public static function getInstance(): PluginManager + { + if (self::$instance === null) { + self::$instance = new PluginManager(); + } + return self::$instance; + } + + // Autoload plugins found in pluginPath + private function autoloadPlugins(): void + { + if (!is_dir($this->pluginPath)) { + return; + } + $directories = array_filter(glob($this->pluginPath . '/*'), 'is_dir'); + foreach ($directories as $dir) { + $pluginName = basename($dir); + $pluginFile = "$dir/$pluginName.php"; + $pluginClass = "RaspAP\\Plugins\\$pluginName\\$pluginName"; // fully qualified class name + + if (file_exists($pluginFile)) { + require_once $pluginFile; + if (class_exists($pluginClass)) { + $plugin = new $pluginClass($this->pluginPath, $pluginName); + $this->registerPlugin($plugin); + } + } + } + } + + // Registers a plugin by its interface implementation + private function registerPlugin(PluginInterface $plugin) + { + $plugin->initialize($this->sidebar); // pass sidebar to initialize method + $this->plugins[] = $plugin; // store the plugin instance + } + + // Returns the sidebar + public function getSidebar(): Sidebar + { + return $this->sidebar; + } + + /** + * Iterates over registered plugins and calls its associated method + * @param string $page + */ + public function handlePageAction(string $page): bool + { + foreach ($this->getInstalledPlugins() as $pluginClass) { + $plugin = new $pluginClass($this->pluginPath, $pluginClass); + + if ($plugin instanceof PluginInterface) { + // Check if this plugin can handle the page action + if ($plugin->handlePageAction($page)) { + return true; + } + } + } + return false; // no plugins handled the page + } + + // Returns all installed plugins with full class names + public function getInstalledPlugins(): array + { + $plugins = []; + if (file_exists($this->pluginPath)) { + $directories = scandir($this->pluginPath); + + foreach ($directories as $directory) { + if ($directory === "." || $directory === "..") continue; + + $pluginClass = "RaspAP\\Plugins\\$directory\\$directory"; + $pluginFile = $this->pluginPath . "/$directory/$directory.php"; + + // Check if the file and class exist + if (file_exists($pluginFile) && class_exists($pluginClass)) { + $plugins[] = $pluginClass; + } + } + } + return $plugins; + } + +} + diff --git a/src/RaspAP/UI/Sidebar.php b/src/RaspAP/UI/Sidebar.php new file mode 100644 index 00000000..62408e27 --- /dev/null +++ b/src/RaspAP/UI/Sidebar.php @@ -0,0 +1,98 @@ + + * @license https://github.com/raspap/raspap-webgui/blob/master/LICENSE + */ + +namespace RaspAP\UI; + +class Sidebar { + private $items = []; + + public function __construct() { + // Load default sidebar items + $this->addItem(_('Dashboard'), 'fa-solid fa-gauge-high', 'wlan0_info', 10); + $this->addItem(_('Hotspot'), 'far fa-dot-circle', 'hostapd_conf', 20, + fn() => RASPI_HOTSPOT_ENABLED + ); + $this->addItem(_('DHCP Server'), 'fas fa-exchange-alt', 'dhcpd_conf', 30, + fn() => RASPI_DHCP_ENABLED && !$_SESSION["bridgedEnabled"] + ); + $this->addItem(_('Ad Blocking'), 'far fa-hand-paper', 'adblock_conf', 40, + fn() => RASPI_ADBLOCK_ENABLED && RASPI_HOTSPOT_ENABLED && !$_SESSION["bridgedEnabled"] + ); + $this->addItem(_('Networking'), 'fas fa-network-wired', 'network_conf', 50, + fn() => RASPI_NETWORK_ENABLED + ); + $this->addItem(_('WiFi client'), 'fas fa-wifi', 'wpa_conf', 60, + fn() => RASPI_WIFICLIENT_ENABLED && !$_SESSION["bridgedEnabled"] + ); + $this->addItem(_('OpenVPN'), 'fas fa-key', 'openvpn_conf', 70, + fn() => RASPI_OPENVPN_ENABLED + ); + $this->addItem(_('WireGuard'), 'ra-wireguard', 'wg_conf', 80, + fn() => RASPI_WIREGUARD_ENABLED + ); + $this->addItem(_(getProviderValue($_SESSION["providerID"], "name")), 'fas fa-shield-alt', 'provider_conf', 90, + fn() => RASPI_VPN_PROVIDER_ENABLED + ); + $this->addItem(_('Authentication'), 'fas fa-user-lock', 'auth_conf', 100, + fn() => RASPI_CONFAUTH_ENABLED + ); + $this->addItem(_('Data usage'), 'fas fa-chart-area', 'data_use', 110, + fn() => RASPI_VNSTAT_ENABLED + ); + $this->addItem(_('RestAPI'), 'fas fa-puzzle-piece', 'restapi_conf', 120, + fn() => RASPI_RESTAPI_ENABLED + ); + $this->addItem(_('System'), 'fas fa-cube', 'system_info', 130, + fn() => RASPI_SYSTEM_ENABLED + ); + $this->addItem(_('About RaspAP'), 'fas fa-info-circle', 'about', 140); + } + + /** + * Adds an item to the sidebar + * @param string $label + * @param string $iconClass + * @param string $link + * @param int $priority + * @param callable $condition + */ + public function addItem(string $label, string $iconClass, string $link, int $priority, callable $condition = null) { + $this->items[] = [ + 'label' => $label, + 'icon' => $iconClass, + 'link' => $link, + 'priority' => $priority, + 'condition' => $condition + ]; + } + + public function getItems(): array { + // Sort items by priority and filter by enabled condition + $filteredItems = array_filter($this->items, function ($item) { + return $item['condition'] === null || call_user_func($item['condition']); + }); + usort($filteredItems, function ($a, $b) { + return $a['priority'] <=> $b['priority']; + }); + return $filteredItems; + } + + public function render() { + foreach ($this->getItems() as $item) { + echo ""; + } + } +} +