From 1fd95e4edf6558bdf97bf58c4205d8ed28be9e7a Mon Sep 17 00:00:00 2001 From: "Joseph.Rawlings" Date: Wed, 3 Dec 2025 15:47:36 -0500 Subject: [PATCH] feat: enhance CreditsPlus and NavbarManager extensions with improved role checks, route handling, and migration logic for better functionality and compatibility across databases --- extensions/Others/CreditsPlus/CreditsPlus.php | 16 ++++ ...000_update_navbar_links_link_type_enum.php | 41 +++++++++- .../NavbarManager/Models/NavbarLink.php | 55 ++++++++++++++ .../Others/NavbarManager/NavbarManager.php | 12 +-- .../View/Components/NavigationLinks.php | 76 ++++++++++++++++--- 5 files changed, 180 insertions(+), 20 deletions(-) diff --git a/extensions/Others/CreditsPlus/CreditsPlus.php b/extensions/Others/CreditsPlus/CreditsPlus.php index 8c92812db..1e881b367 100644 --- a/extensions/Others/CreditsPlus/CreditsPlus.php +++ b/extensions/Others/CreditsPlus/CreditsPlus.php @@ -65,6 +65,22 @@ class CreditsPlus extends Extension // Use view composer to add credits data to navigation View::composer('components.navigation.index', function ($view) use ($extension, $extensionPath) { try { + // Don't run in admin panel - this is crucial to avoid conflicts + if (app()->runningInConsole()) { + return; + } + + // Check if we're in admin context + $currentPath = request()->path(); + if (str_starts_with($currentPath, 'admin') || str_contains($currentPath, '/admin/')) { + return; + } + + // Don't run for API routes + if (str_starts_with($currentPath, 'api')) { + return; + } + if (!Auth::check()) { return; } diff --git a/extensions/Others/NavbarManager/Database/migrations/2024_07_02_230000_update_navbar_links_link_type_enum.php b/extensions/Others/NavbarManager/Database/migrations/2024_07_02_230000_update_navbar_links_link_type_enum.php index 16fedded2..db7d8d820 100644 --- a/extensions/Others/NavbarManager/Database/migrations/2024_07_02_230000_update_navbar_links_link_type_enum.php +++ b/extensions/Others/NavbarManager/Database/migrations/2024_07_02_230000_update_navbar_links_link_type_enum.php @@ -1,19 +1,52 @@ getDriverName(); + + if ($driver === 'mysql') { + // For MySQL, use ALTER TABLE to modify enum + DB::statement("ALTER TABLE navbar_links MODIFY COLUMN link_type ENUM('route', 'path', 'url', 'product', 'category') DEFAULT 'route'"); + } else { + // For other databases (PostgreSQL, SQLite), drop and recreate column + Schema::table('navbar_links', function (Blueprint $table) { + $table->string('link_type_temp')->default('route'); + }); + + DB::table('navbar_links')->update(['link_type_temp' => DB::raw('link_type')]); + + Schema::table('navbar_links', function (Blueprint $table) { + $table->dropColumn('link_type'); + }); + + Schema::table('navbar_links', function (Blueprint $table) { + $table->string('link_type')->default('route')->after('type'); + }); + + DB::table('navbar_links')->update(['link_type' => DB::raw('link_type_temp')]); + + Schema::table('navbar_links', function (Blueprint $table) { + $table->dropColumn('link_type_temp'); + }); + } } public function down() { - // Revert back to original enum values - DB::statement("ALTER TABLE navbar_links MODIFY COLUMN link_type ENUM('route', 'path', 'url') DEFAULT 'route'"); + $driver = Schema::getConnection()->getDriverName(); + + if ($driver === 'mysql') { + DB::statement("ALTER TABLE navbar_links MODIFY COLUMN link_type ENUM('route', 'path', 'url') DEFAULT 'route'"); + } else { + // For rollback on other databases, just keep as string + // Data with 'product' or 'category' will remain but may cause issues + } } }; diff --git a/extensions/Others/NavbarManager/Models/NavbarLink.php b/extensions/Others/NavbarManager/Models/NavbarLink.php index 6439d85a8..9ff733c17 100644 --- a/extensions/Others/NavbarManager/Models/NavbarLink.php +++ b/extensions/Others/NavbarManager/Models/NavbarLink.php @@ -30,6 +30,61 @@ class NavbarLink extends Model 'route_params' => 'json', ]; + /** + * Set route_params attribute - convert repeater format to associative array + */ + public function setRouteParamsAttribute($value) + { + if (is_array($value) && !empty($value)) { + // Check if it's in repeater format [['key' => 'foo', 'value' => 'bar']] + $firstItem = reset($value); + if (is_array($firstItem) && isset($firstItem['key']) && isset($firstItem['value'])) { + // Convert to associative array + $params = []; + foreach ($value as $item) { + if (isset($item['key']) && isset($item['value'])) { + $params[$item['key']] = $item['value']; + } + } + $this->attributes['route_params'] = json_encode($params); + return; + } + } + + // Otherwise store as-is + $this->attributes['route_params'] = is_string($value) ? $value : json_encode($value); + } + + /** + * Get route_params attribute - convert to repeater format for Filament + */ + public function getRouteParamsAttribute($value) + { + if (empty($value)) { + return null; + } + + $decoded = is_string($value) ? json_decode($value, true) : $value; + + if (!is_array($decoded)) { + return null; + } + + // If it's already in repeater format, return as-is + $firstItem = reset($decoded); + if (is_array($firstItem) && isset($firstItem['key']) && isset($firstItem['value'])) { + return $decoded; + } + + // Convert associative array to repeater format for Filament + $repeaterFormat = []; + foreach ($decoded as $key => $val) { + $repeaterFormat[] = ['key' => $key, 'value' => $val]; + } + + return $repeaterFormat; + } + public function parent() { return $this->belongsTo(NavbarLink::class, 'parent_id'); diff --git a/extensions/Others/NavbarManager/NavbarManager.php b/extensions/Others/NavbarManager/NavbarManager.php index 1df2ae663..dd9b8d819 100644 --- a/extensions/Others/NavbarManager/NavbarManager.php +++ b/extensions/Others/NavbarManager/NavbarManager.php @@ -123,9 +123,9 @@ class NavbarManager extends Extension case 'always_hidden': return false; case 'clients_only': - return auth()->check() && auth()->user()->role_id === null; + return auth()->check() && auth()->user()->role === null; case 'admin_only': - return auth()->check() && auth()->user()->role_id !== null; + return auth()->check() && auth()->user()->role !== null; case 'guests_only': return !auth()->check(); default: @@ -159,9 +159,11 @@ class NavbarManager extends Extension $formatted['route'] = $link->route_name; $formatted['spa'] = true; // Enable SPA for route links if ($link->route_params) { - $params = is_string($link->route_params) - ? json_decode($link->route_params, true) - : $link->route_params; + // Get raw attribute to avoid accessor transformation + $rawParams = $link->getAttributes()['route_params'] ?? null; + $params = is_string($rawParams) + ? json_decode($rawParams, true) + : $rawParams; if ($params && is_array($params)) { $formatted['params'] = $params; } diff --git a/extensions/Others/NavbarManager/View/Components/NavigationLinks.php b/extensions/Others/NavbarManager/View/Components/NavigationLinks.php index d9621ab55..a3e6dac77 100644 --- a/extensions/Others/NavbarManager/View/Components/NavigationLinks.php +++ b/extensions/Others/NavbarManager/View/Components/NavigationLinks.php @@ -60,9 +60,9 @@ class NavigationLinks extends Component case 'always_hidden': return false; case 'clients_only': - return auth()->check() && auth()->user()->role_id === null; + return auth()->check() && auth()->user()->role === null; case 'admin_only': - return auth()->check() && auth()->user()->role_id !== null; + return auth()->check() && auth()->user()->role !== null; case 'guests_only': return !auth()->check(); default: @@ -81,9 +81,11 @@ class NavigationLinks extends Component 'priority' => $link->sort_order, ]; - // Add icon - if ($link->icon) { - $formatted['icon'] = $link->icon; + // Add icon with validation and strip -line/-fill suffix + if ($link->icon && $this->isValidIcon($link->icon)) { + // Remove -line or -fill suffix since Navigation::markActiveRoute will add it + $baseIcon = preg_replace('/-(?:line|fill)$/', '', $link->icon); + $formatted['icon'] = $baseIcon; } // Handle different link types @@ -91,10 +93,13 @@ class NavigationLinks extends Component case 'route': if ($link->route_name) { $formatted['route'] = $link->route_name; + $formatted['spa'] = true; // Enable SPA for route links if ($link->route_params) { - $params = is_string($link->route_params) - ? json_decode($link->route_params, true) - : $link->route_params; + // Get raw attribute to avoid accessor transformation + $rawParams = $link->getAttributes()['route_params'] ?? null; + $params = is_string($rawParams) + ? json_decode($rawParams, true) + : $rawParams; if ($params && is_array($params)) { $formatted['params'] = $params; } @@ -104,10 +109,50 @@ class NavigationLinks extends Component } break; case 'path': - $formatted['direct_url'] = $link->custom_path; - break; case 'url': - $formatted['direct_url'] = $link->custom_url; + // Use navbar redirect route + $formatted['route'] = 'navbar.redirect'; + $formatted['params'] = ['linkId' => $link->id]; + $formatted['spa'] = false; // Disable SPA for external links + break; + case 'product': + if ($link->product_id) { + try { + $product = \App\Models\Product::with('category')->find($link->product_id); + if ($product && $product->category) { + $formatted['route'] = 'products.show'; + $formatted['params'] = [ + 'category' => $product->category->slug ?? $product->category->id, + 'product' => $product->slug ?? $product->id + ]; + $formatted['spa'] = true; + } else { + return null; + } + } catch (\Exception $e) { + return null; + } + } else { + return null; + } + break; + case 'category': + if ($link->category_id) { + try { + $category = \App\Models\Category::find($link->category_id); + if ($category) { + $formatted['route'] = 'category.show'; + $formatted['params'] = ['category' => $category->slug ?? $category->id]; + $formatted['spa'] = true; + } else { + return null; + } + } catch (\Exception $e) { + return null; + } + } else { + return null; + } break; } @@ -136,4 +181,13 @@ class NavigationLinks extends Component return null; } } + + private function isValidIcon($icon) + { + // Basic validation for RemixIcon format + if (!preg_match('/^ri-[\w-]+$/', $icon)) { + return false; + } + return true; + } }