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 "";
+ }
+ }
+}
+