mirror of
https://github.com/RaspAP/raspap-webgui.git
synced 2024-11-25 00:50:29 +00:00
Merge pull request #1693 from RaspAP/feat/plugin-architecture
Plugin architecture to support user add-ons
This commit is contained in:
commit
b74db9464e
9 changed files with 322 additions and 148 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -3,6 +3,7 @@ node_modules
|
|||
yarn-error.log
|
||||
*.swp
|
||||
includes/config.php
|
||||
plugins/
|
||||
rootCA.pem
|
||||
vendor
|
||||
.env
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
?>
|
||||
<div class="row g-0">
|
||||
<div class="col-4 ms-2 sidebar-brand-icon">
|
||||
<img src="app/img/raspAP-logo.php" class="navbar-logo" width="60" height="60">
|
||||
</div>
|
||||
<div class="col ml-2">
|
||||
<div class="ml-1 sb-status">Status</div>
|
||||
<div class="info-item-xs"><span class="icon">
|
||||
<i class="fas fa-circle <?php echo ($hostapd_led); ?>"></i></span> <?php echo _("Hotspot").' '. _($hostapd_status); ?>
|
||||
</div>
|
||||
<div class="info-item-xs"><span class="icon">
|
||||
<i class="fas fa-circle <?php echo ($memused_led); ?>"></i></span> <?php echo _("Mem Use").': '. htmlspecialchars(strval($memused), ENT_QUOTES); ?>%
|
||||
</div>
|
||||
<div class="info-item-xs"><span class="icon">
|
||||
<i class="fas fa-circle <?php echo ($cputemp_led); ?>"></i></span> <?php echo _("CPU").': '. htmlspecialchars($cputemp, ENT_QUOTES); ?>°C
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php
|
||||
}
|
||||
|
||||
|
|
|
@ -1,8 +1,27 @@
|
|||
<?php
|
||||
$extraFooterScripts = array();
|
||||
$page = $_SERVER['PATH_INFO'];
|
||||
// handle page actions
|
||||
switch ($page) {
|
||||
<?php
|
||||
|
||||
$pluginManager = \RaspAP\Plugins\PluginManager::getInstance();
|
||||
|
||||
// Get the requested page
|
||||
$extraFooterScripts = array();
|
||||
$page = $_SERVER['PATH_INFO'];
|
||||
|
||||
// Check if any plugin wants to handle the request
|
||||
if (!$pluginManager->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);
|
||||
}
|
||||
?>
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,89 +1,15 @@
|
|||
<div class="row g-0">
|
||||
<div class="col-4 ms-2 sidebar-brand-icon">
|
||||
<img src="app/img/raspAP-logo.php" class="navbar-logo" width="60" height="60">
|
||||
</div>
|
||||
<div class="col ml-2">
|
||||
<div class="ml-1 sb-status">Status</div>
|
||||
<div class="info-item-xs"><span class="icon">
|
||||
<i class="fas fa-circle <?php echo ($hostapd_led); ?>"></i></span> <?php echo _("Hotspot").' '. _($hostapd_status); ?>
|
||||
</div>
|
||||
<div class="info-item-xs"><span class="icon">
|
||||
<i class="fas fa-circle <?php echo ($memused_led); ?>"></i></span> <?php echo _("Mem Use").': '. htmlspecialchars(strval($memused), ENT_QUOTES); ?>%
|
||||
</div>
|
||||
<div class="info-item-xs"><span class="icon">
|
||||
<i class="fas fa-circle <?php echo ($cputemp_led); ?>"></i></span> <?php echo _("CPU").': '. htmlspecialchars($cputemp, ENT_QUOTES); ?>°C
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="sb-nav-link-icon px-2">
|
||||
<a class="nav-link" href="wlan0_info"><i class="sb-nav-link-icon fa-solid fa-gauge-high fa-fw mr-2"></i><span class="nav-label"><?php echo _("Dashboard"); ?></span></a>
|
||||
</div>
|
||||
<?php if (RASPI_HOTSPOT_ENABLED) : ?>
|
||||
<div class="sb-nav-link-icon px-2">
|
||||
<a class="nav-link" href="hostapd_conf"><i class="sb-nav-link-icon far fa-dot-circle fa-fw mr-2"></i><span class="nav-label"><?php echo _("Hotspot"); ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (RASPI_DHCP_ENABLED && !$_SESSION["bridgedEnabled"]) : ?>
|
||||
<div class="sb-nav-link-icon px-2">
|
||||
<a class="nav-link" href="dhcpd_conf"><i class="sb-nav-link-icon fas fa-exchange-alt fa-fw mr-2"></i><span class="nav-label"><?php echo _("DHCP Server"); ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (RASPI_ADBLOCK_ENABLED && !$_SESSION["bridgedEnabled"]) : ?>
|
||||
<div class="sb-nav-link-icon px-2">
|
||||
<a class="nav-link" href="adblock_conf"><i class="sb-nav-link-icon far fa-hand-paper fa-fw mr-2"></i><span class="nav-label"><?php echo _("Ad Blocking"); ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (RASPI_NETWORK_ENABLED) : ?>
|
||||
<div class="sb-nav-link-icon px-2">
|
||||
<a class="nav-link" href="network_conf"><i class="sb-nav-link-icon fas fa-network-wired fa-fw mr-2"></i><span class="nav-label"><?php echo _("Networking"); ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (RASPI_WIFICLIENT_ENABLED && !$_SESSION["bridgedEnabled"]) : ?>
|
||||
<div class="sb-nav-link-icon px-2">
|
||||
<a class="nav-link" href="wpa_conf"><i class="sb-nav-link-icon fas fa-wifi fa-fw mr-2"></i><span class="nav-label"><?php echo _("WiFi client"); ?></span></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (RASPI_OPENVPN_ENABLED) : ?>
|
||||
<div class="sb-nav-link-icon px-2">
|
||||
<a class="nav-link" href="openvpn_conf"><i class="sb-nav-link-icon fas fa-key fa-fw mr-2"></i><span class="nav-label"><?php echo _("OpenVPN"); ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (RASPI_WIREGUARD_ENABLED) : ?>
|
||||
<div class="sb-nav-link-icon px-2">
|
||||
<a class="nav-link" href="wg_conf"><i class="sb-nav-link-icon ra-wireguard mr-2"></i><span class="nav-label"><?php echo _("WireGuard"); ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (RASPI_VPN_PROVIDER_ENABLED) : ?>
|
||||
<div class="sb-nav-link-icon px-2">
|
||||
<a class="nav-link" href="provider_conf"><i class="sb-nav-link-icon fas fa-shield-alt fa-fw mr-2"></i><span class="nav-label"><?php echo _(getProviderValue($_SESSION["providerID"], "name")); ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (RASPI_TORPROXY_ENABLED) : ?>
|
||||
<div class="sb-nav-link-icon px-2">
|
||||
<a class="nav-link" href="torproxy_conf"><i class="sb-nav-link-icon fas fa-eye-slash fa-fw mr-2"></i><span class="nav-label"><?php echo _("TOR proxy"); ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (RASPI_CONFAUTH_ENABLED) : ?>
|
||||
<div class="sb-nav-link-icon px-2">
|
||||
<a class="nav-link" href="auth_conf"><i class="sb-nav-link-icon fas fa-user-lock fa-fw mr-2"></i><span class="nav-label"><?php echo _("Authentication"); ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (RASPI_VNSTAT_ENABLED) : ?>
|
||||
<div class="sb-nav-link-icon px-2">
|
||||
<a class="nav-link" href="data_use"><i class="sb-nav-link-icon fas fa-chart-area fa-fw mr-2"></i><span class="nav-label"><?php echo _("Data usage"); ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (RASPI_RESTAPI_ENABLED) : ?>
|
||||
<div class="sb-nav-link-icon px-2">
|
||||
<a class="nav-link" href="restapi_conf"><i class="sb-nav-link-icon fas fa-puzzle-piece mr-2"></i><span class="nav-label"><?php echo _("RestAPI"); ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if (RASPI_SYSTEM_ENABLED) : ?>
|
||||
<div class="sb-nav-link-icon px-2">
|
||||
<a class="nav-link" href="system_info"><i class="sb-nav-link-icon fas fa-cube fa-fw mr-2"></i><span class="nav-label"><?php echo _("System"); ?></a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<div class="sb-nav-link-icon px-2">
|
||||
<a class="nav-link" href="about"><i class="sb-nav-link-icon fas fa-info-circle fa-fw mr-2"></i><span class="nav-label"><?php echo _("About RaspAP"); ?></a>
|
||||
</div>
|
||||
<?php
|
||||
|
||||
use RaspAP\Plugins\PluginManager;
|
||||
|
||||
$pluginManager = PluginManager::getInstance();
|
||||
|
||||
// Display logo and status LEDs
|
||||
renderStatus($hostapd_led, $hostapd_status,
|
||||
$memused_led, $memused,
|
||||
$cputemp_led, $cputemp);
|
||||
|
||||
// Render sidebar via the PluginManager
|
||||
$sidebar = $pluginManager->getSidebar();
|
||||
$sidebar->render();
|
||||
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Manage Tor Proxy configuration
|
||||
*/
|
||||
function DisplayTorProxyConfig()
|
||||
{
|
||||
|
||||
exec('cat '. RASPI_TORPROXY_CONFIG, $return);
|
||||
exec('pidof tor | wc -l', $torproxystatus);
|
||||
|
||||
$arrConfig = array();
|
||||
foreach ($return as $a) {
|
||||
if ($a[0] != "#") {
|
||||
$arrLine = explode(" ", $a);
|
||||
$arrConfig[$arrLine[0]]=$arrLine[1];
|
||||
}
|
||||
}
|
||||
|
||||
echo renderTemplate(
|
||||
"torproxy", compact(
|
||||
"status",
|
||||
"torproxystatus"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*
|
||||
*/
|
||||
function SaveTORAndVPNConfig()
|
||||
{
|
||||
if (isset($_POST['SaveTORProxySettings'])) {
|
||||
// TODO
|
||||
} elseif (isset($_POST['StartTOR'])) {
|
||||
echo "Attempting to start TOR";
|
||||
exec('sudo systemctl start tor.service', $return);
|
||||
foreach ($return as $line) {
|
||||
$status->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');
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
|
@ -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();
|
||||
?>
|
||||
|
|
34
src/RaspAP/Plugins/PluginInterface.php
Normal file
34
src/RaspAP/Plugins/PluginInterface.php
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Plugin Interface
|
||||
*
|
||||
* @description Basic plugin interface for RaspAP
|
||||
* @author Bill Zimmerman <billzimmerman@gmail.com>
|
||||
* @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;
|
||||
}
|
||||
|
117
src/RaspAP/Plugins/PluginManager.php
Normal file
117
src/RaspAP/Plugins/PluginManager.php
Normal file
|
@ -0,0 +1,117 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Plugin Manager class
|
||||
*
|
||||
* @description Class supporting user plugins to extend RaspAP
|
||||
* @author Bill Zimmerman <billzimmerman@gmail.com>
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
98
src/RaspAP/UI/Sidebar.php
Normal file
98
src/RaspAP/UI/Sidebar.php
Normal file
|
@ -0,0 +1,98 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Sidebar UI class
|
||||
*
|
||||
* @description A sidebar class for the RaspAP UI, extendable by user plugins
|
||||
* @author Bill Zimmerman <billzimmerman@gmail.com>
|
||||
* @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 "<div class=\"sb-nav-link-icon px-2\">
|
||||
<a class=\"nav-link\" href=\"{$item['link']}\">
|
||||
<i class=\"sb-nav-link-icon {$item['icon']} fa-fw mr-2\"></i>
|
||||
<span class=\"nav-label\">{$item['label']}</span>
|
||||
</a>
|
||||
</div>";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in a new issue