Files
Virtnet.bond/extensions/Others/NavbarManager/NavbarManager.php

295 lines
12 KiB
PHP

<?php
namespace Paymenter\Extensions\Others\NavbarManager;
use App\Classes\Extension\Extension;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Facades\Event;
use Illuminate\Support\HtmlString;
use Paymenter\Extensions\Others\NavbarManager\Admin\Resources\NavbarLinkResource;
use Paymenter\Extensions\Others\NavbarManager\Models\NavbarLink;
class NavbarManager extends Extension
{
private static bool $migrationAttempted = false;
public function getConfig($values = [])
{
try {
return [
[
'name' => 'Notice',
'type' => 'placeholder',
'label' => new HtmlString('You can use this extension to manage custom navigation links. To manage navbar links, go to <a class="text-primary-600" href="' . NavbarLinkResource::getUrl() . '">Navbar Manager</a>.'),
],
];
} catch (\Exception $e) {
return [
[
'name' => 'Notice',
'type' => 'placeholder',
'label' => new HtmlString('You can use this extension to manage custom navigation links. You\'ll need to enable this extension above to get started.'),
],
];
}
}
public function enabled()
{
// This method is called only when the extension is being enabled
// Run migrations or initial setup here
try {
Artisan::call('migrate', [
'--path' => 'extensions/Others/NavbarManager/database/migrations',
'--force' => true
]);
} catch (\Exception $e) {
\Log::error('NavbarManager: Migration failed', ['error' => $e->getMessage()]);
}
}
public function boot()
{
// Register view service provider
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
Event::listen('navigation', function () {
return $this->getNavigationLinks('primary');
});
Event::listen('navigation.account-dropdown', function () {
return $this->getNavigationLinks('account');
});
Event::listen('navigation.dashboard', function () {
return $this->getNavigationLinks('dashboard');
});
// Note: Admin resources are automatically discovered by Paymenter's AdminPanelProvider
// from the Admin/Resources folder - no manual registration needed
}
private function getNavigationLinks($type)
{
try {
if (!\Schema::hasTable('navbar_links')) {
if (!self::$migrationAttempted) {
self::$migrationAttempted = true;
try {
Artisan::call('migrate', [
'--path' => 'extensions/Others/NavbarManager/database/migrations',
'--force' => true,
]);
} catch (\Exception $migrationException) {
\Log::error('NavbarManager: Failed to run migrations', [
'error' => $migrationException->getMessage(),
]);
}
}
// Table still missing, skip navigation injection to prevent 500s
if (!\Schema::hasTable('navbar_links')) {
\Log::warning('NavbarManager: navbar_links table missing. Skipping navigation injection.', [
'type' => $type,
]);
return [];
}
}
$links = NavbarLink::where('type', $type)
->where('is_active', true)
->whereNull('parent_id') // Only get top-level links
->orderBy('sort_order')
->with('children')
->get();
$navLinks = [];
foreach ($links as $link) {
if ($this->shouldShowLink($link)) {
$formattedLink = $this->formatLink($link);
if ($formattedLink) {
$navLinks[] = $formattedLink;
}
}
}
return $navLinks;
} catch (\Exception $e) {
// Log error but don't break navigation
\Log::error('NavbarManager: Error loading navigation links', [
'type' => $type,
'error' => $e->getMessage(),
'trace' => $e->getTraceAsString()
]);
return [];
}
}
private function shouldShowLink($link)
{
try {
switch ($link->condition) {
case 'always_visible':
return true;
case 'always_hidden':
return false;
case 'clients_only':
return auth()->check() && auth()->user()->role === null;
case 'admin_only':
return auth()->check() && auth()->user()->role !== null;
case 'guests_only':
return !auth()->check();
default:
return true;
}
} catch (\Exception $e) {
// If there's an error checking conditions, default to visible
return true;
}
}
private function formatLink($link)
{
try {
$formatted = [
'name' => $link->name,
'priority' => $link->sort_order,
];
// 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
switch ($link->link_type) {
case 'route':
if ($link->route_name) {
$formatted['route'] = $link->route_name;
$formatted['spa'] = true; // Enable SPA for route links
if ($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;
}
}
} else {
return null;
}
break;
case 'path':
case '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) {
// Use products show route (requires both category and product slugs)
$formatted['route'] = 'products.show';
$formatted['params'] = [
'category' => $product->category->slug ?? $product->category->id,
'product' => $product->slug ?? $product->id
];
$formatted['spa'] = true;
} else {
return null; // Product or category not found
}
} 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) {
// Use category show route
$formatted['route'] = 'category.show';
$formatted['params'] = ['category' => $category->slug ?? $category->id];
$formatted['spa'] = true;
} else {
return null; // Category not found
}
} catch (\Exception $e) {
return null;
}
} else {
return null;
}
break;
}
// Handle children (dropdown items)
if ($link->children && $link->children->count() > 0) {
$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
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;
}
}