feat: refine NavbarManager to restrict navigation modifications in admin context, enhance route handling, and improve icon validation, ensuring better separation of frontend and admin functionalities

This commit is contained in:
2025-12-03 15:59:50 -05:00
parent 1fd95e4edf
commit fe7c0e953d
4 changed files with 162 additions and 20 deletions

View File

@@ -20,11 +20,11 @@ class NavbarLinkResource extends Resource
{
protected static ?string $model = NavbarLink::class;
protected static string|\BackedEnum|null $navigationIcon = 'heroicon-o-bars-3';
protected static ?string $navigationIcon = 'heroicon-o-bars-3';
protected static ?string $navigationLabel = 'Navbar Manager';
protected static string|\UnitEnum|null $navigationGroup = 'Content';
protected static ?string $navigationGroup = 'Content';
public static function getAvailableRoutes(): array
{

View File

@@ -50,26 +50,79 @@ class NavbarManager extends Extension
public function boot()
{
// Register view service provider
// Don't run any navigation modifications in admin or console
if ($this->isAdminContext()) {
// Only register Filament resource in admin
$this->registerFilamentResource();
return;
}
// Register view service provider (frontend only)
app()->register(\Paymenter\Extensions\Others\NavbarManager\Providers\ViewServiceProvider::class);
// Register custom routes for navbar links
require __DIR__ . '/routes/web.php';
// Register event listeners for navigation - dodaće naše linkove uz postojeće
// Register event listeners for navigation - only for frontend
Event::listen('navigation', function () {
return $this->getNavigationLinks('primary');
if (!$this->isAdminContext()) {
return $this->getNavigationLinks('primary');
}
return [];
});
Event::listen('navigation.account-dropdown', function () {
return $this->getNavigationLinks('account');
if (!$this->isAdminContext()) {
return $this->getNavigationLinks('account');
}
return [];
});
Event::listen('navigation.dashboard', function () {
return $this->getNavigationLinks('dashboard');
if (!$this->isAdminContext()) {
return $this->getNavigationLinks('dashboard');
}
return [];
});
}
// Register the Filament resource
/**
* Check if we're in admin context
*/
private function isAdminContext(): bool
{
if (app()->runningInConsole()) {
return false; // Allow in console for migrations, etc.
}
try {
$currentPath = request()->path();
// Check for admin routes
if (str_starts_with($currentPath, 'admin') ||
str_contains($currentPath, '/admin/') ||
request()->is('admin') ||
request()->is('admin/*')) {
return true;
}
// Check for Filament routes
if (str_starts_with($currentPath, 'filament') ||
request()->is('filament/*')) {
return true;
}
return false;
} catch (\Exception $e) {
return false;
}
}
/**
* Register Filament resource
*/
private function registerFilamentResource(): void
{
try {
\Filament\Facades\Filament::serving(function () {
\Filament\Facades\Filament::getCurrentPanel()
@@ -78,7 +131,9 @@ class NavbarManager extends Extension
]);
});
} catch (\Exception $e) {
// Resource not yet available
\Log::error('NavbarManager: Failed to register Filament resource', [
'error' => $e->getMessage()
]);
}
}
@@ -248,6 +303,61 @@ class NavbarManager extends Extension
}
}
private function isValidIcon($icon)
{
// Basic validation for RemixIcon format
if (!preg_match('/^ri-[\w-]+$/', $icon)) {
return false;
}
// List of common valid RemixIcon names to avoid errors (without -line/-fill suffix)
$commonIcons = [
'ri-home', 'ri-user', 'ri-settings', 'ri-dashboard',
'ri-menu', 'ri-link', 'ri-external-link', 'ri-folder',
'ri-file', 'ri-edit', 'ri-delete-bin', 'ri-add',
'ri-navigation', 'ri-admin', 'ri-function', 'ri-archive-stack',
'ri-receipt', 'ri-customer-service', 'ri-settings-3', 'ri-megaphone',
'ri-shopping-bag', 'ri-check', 'ri-close', 'ri-arrow-down-s'
];
// Remove -line or -fill suffix if present to get base icon name
$baseIcon = preg_replace('/-(?:line|fill)$/', '', $icon);
// If it's in our common list, it's definitely valid
if (in_array($baseIcon, $commonIcons)) {
return true;
}
// For other icons, just check the format (basic validation)
// In a production environment, you might want to check if the SVG file actually exists
return true;
}
}
$children = [];
foreach ($link->children->where('is_active', true) as $child) {
if ($this->shouldShowLink($child)) {
$childFormatted = $this->formatLink($child);
if ($childFormatted) {
$children[] = $childFormatted;
}
}
}
if (!empty($children)) {
$formatted['children'] = $children;
}
}
return $formatted;
} catch (\Exception $e) {
\Log::error('NavbarManager: Error formatting link', [
'link_id' => $link->id ?? 'unknown',
'error' => $e->getMessage()
]);
return null;
}
}
private function isValidIcon($icon)
{
// Basic validation for RemixIcon format

View File

@@ -10,17 +10,44 @@ class ViewServiceProvider extends ServiceProvider
{
public function boot()
{
// Register view namespace
$this->loadViewsFrom(__DIR__ . '/../resources/views', 'navbar-manager');
// Load migrations from the extension so they run when the extension is enabled
// Always load migrations regardless of context
$this->loadMigrationsFrom(__DIR__ . '/../Database/migrations');
// Register Blade components
Blade::componentNamespace('Paymenter\\Extensions\\Others\\NavbarManager\\View\\Components', 'navbar-manager');
// Only register views and components if not in admin context
if (!$this->isAdminContext()) {
// Register view namespace
$this->loadViewsFrom(__DIR__ . '/../resources/views', 'navbar-manager');
// Register global Blade directive for easy navbar usage
Blade::directive('navbarManagerLinks', function ($expression) {
return "<?php echo \Paymenter\Extensions\Others\NavbarManager\Helpers\NavbarHelper::render($expression); ?>";
});
// Register Blade components
Blade::componentNamespace('Paymenter\\Extensions\\Others\\NavbarManager\\View\\Components', 'navbar-manager');
// Register global Blade directive for easy navbar usage
Blade::directive('navbarManagerLinks', function ($expression) {
return "<?php echo \Paymenter\Extensions\Others\NavbarManager\Helpers\NavbarHelper::render($expression); ?>";
});
}
}
/**
* Check if we're in admin context
*/
private function isAdminContext(): bool
{
if (app()->runningInConsole()) {
return false;
}
try {
$currentPath = request()->path();
return str_starts_with($currentPath, 'admin') ||
str_contains($currentPath, '/admin/') ||
str_starts_with($currentPath, 'filament') ||
request()->is('admin') ||
request()->is('admin/*') ||
request()->is('filament/*');
} catch (\Exception $e) {
return false;
}
}
}

View File

@@ -3,7 +3,8 @@
use Illuminate\Support\Facades\Route;
use Paymenter\Extensions\Others\NavbarManager\Models\NavbarLink;
Route::group(['prefix' => 'navbar'], function () {
// Only register frontend routes, not in admin
Route::group(['prefix' => 'navbar', 'middleware' => ['web']], function () {
Route::get('/redirect/{linkId}', function($linkId) {
try {
$link = NavbarLink::findOrFail($linkId);
@@ -19,6 +20,10 @@ Route::group(['prefix' => 'navbar'], function () {
abort(404);
} catch (\Exception $e) {
\Log::error('NavbarManager: Redirect failed', [
'link_id' => $linkId,
'error' => $e->getMessage()
]);
abort(404);
}
})->name('navbar.redirect');