From 3baf3d2545ba6ece6b74a64c0def59bd0774cf54 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Wed, 10 Jun 2026 16:34:12 +0000
Subject: [PATCH] =Laid the groundwork for an improved DashboardManager.php setup. Have to put it aside so I can get the dang Northeh done though.

---
 JVBase.php                                  |   10 
 inc/managers/Dashboard/Section.php          |   55 +++
 inc/managers/Dashboard/DashboardManager.php |  659 +++++++++++++++++++++++++++++++++++++++++
 inc/managers/DashboardManager.php           |   11 
 inc/managers/RoleManager.php                |   16 
 inc/integrations/Integrations.php           |   16 
 inc/managers/Dashboard/DashboardPage.php    |  159 +++++++++
 inc/managers/_setup.php                     |    8 
 jvb.php                                     |    2 
 base/options.php                            |    5 
 inc/managers/Dashboard/_setup.php           |    8 
 11 files changed, 926 insertions(+), 23 deletions(-)

diff --git a/JVBase.php b/JVBase.php
index bf0db8a..ea4f09c 100644
--- a/JVBase.php
+++ b/JVBase.php
@@ -11,7 +11,7 @@
 use JVBase\managers\LoginManager;
 use JVBase\managers\MagicLinkManager;
 use JVBase\managers\queue\Queue;
-use JVBase\managers\DashboardManager;
+use JVBase\managers\Dashboard\DashboardManager;
 use JVBase\managers\DirectoryManager;
 use JVBase\managers\ReferralManager;
 use JVBase\managers\RoleManager;
@@ -132,9 +132,6 @@
 			$this->routes['referral'] = new ReferralRoutes();
 		}
 
-		if (Site::has('dashboard')) {
-			$this->managers['dash'] = new DashboardManager();
-		}
 
 		if (Site::hasIntegration('square')) {
 			$this->routes['square'] = new IntegrationsSquareRoutes();
@@ -198,6 +195,11 @@
 			$this->routes['invites'] = new Invitations();
 		}
 
+
+		if (Site::has('dashboard')) {
+			$this->managers['dash'] = new DashboardManager();
+		}
+
 		$this->setupIntegrations();
 
 		add_action('wp_footer', [$this, 'additionalActions']);
diff --git a/base/options.php b/base/options.php
index 50f0979..7f7cc3f 100644
--- a/base/options.php
+++ b/base/options.php
@@ -60,8 +60,9 @@
 			$this->fields->addField(
 				'open_to_public',
 				[
-					'type'	=> 'true_false',
-					'label'	=> 'Open to Public?'
+					'type'		=> 'true_false',
+					'label'		=> 'Open to Public?',
+					'default'	=> 1,
 				]
 			);
 
diff --git a/inc/integrations/Integrations.php b/inc/integrations/Integrations.php
index ef1a135..cb08b13 100644
--- a/inc/integrations/Integrations.php
+++ b/inc/integrations/Integrations.php
@@ -66,7 +66,7 @@
 	 * Used for UI rendering in admin interfaces
 	 */
 	public string $title;  // Human-readable service name (e.g., 'Google My Business')
-	public string $icon;   // Phosphoricons icon slug
+	public string $icon = '';   // Phosphoricons icon slug
 
 	/**
 	 * Credentials & State
@@ -2714,6 +2714,15 @@
 		return $this->title;
 	}
 
+	public static function title():string
+	{
+		return (new static())->getTitle();
+	}
+	public static function icon():string
+	{
+		return (new static())->getIcon();
+	}
+
 	/*********************************************************************
 		RENDERING
 	 *********************************************************************/
@@ -3535,4 +3544,9 @@
 	{
 		return [];
 	}
+
+	public function getIcon():string
+	{
+		return $this->icon;
+	}
 }
diff --git a/inc/managers/Dashboard/DashboardManager.php b/inc/managers/Dashboard/DashboardManager.php
new file mode 100644
index 0000000..2c4ae50
--- /dev/null
+++ b/inc/managers/Dashboard/DashboardManager.php
@@ -0,0 +1,659 @@
+<?php
+namespace JVBase\managers\Dashboard;
+
+use JetBrains\PhpStorm\NoReturn;
+use JVBase\forms\TaxonomySelector;
+use JVBase\base\Site;
+use JVBase\managers\Cache;
+use JVBase\managers\RoleManager;use JVBase\registrar\Registrar;
+use JVBase\ui\Navigation;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+class DashboardManager {
+	protected array $pages = [];
+	protected array $sections = [];
+	protected Cache $cache;
+	public function __construct() {
+		$this->cache = Cache::for('dashboard')->connect('post');
+		if (JVB_TESTING) {
+			$this->cache->flush();
+		}
+		$this->buildPages();
+		$this->buildSections();
+		//Since this is loaded via JVBase, it's already running on init
+		$this->registerDashboard();
+		$this->registerHooks();
+	}
+	protected function registerDashboard():void
+	{
+		$dash = Registrar::forPost('dash', 'Dashboard', 'Dashboards');
+		$dash->setIcon('gauge')
+			->make([
+				'show_in_admin_bar'	=> false,
+				'rewrite'		=> [
+					'slug'			=> 'dash',
+					'with_front' 	=> false,
+				],
+				'supports'		=> [ 'title', 'editor', 'custom-fields'],
+				'hierarchical'	=> true
+			])->setAll([
+				'system'
+			]);
+	}
+
+	protected function registerHooks():void
+	{
+
+		add_action('template_redirect', [$this, 'handleRedirects']);
+		add_action('template_include', [$this, 'dashboardTemplates']);
+		add_action('admin_init', [$this, 'redirectFromAdmin']);
+		add_action('wp_enqueue_scripts', [$this, 'dashboardScripts'], 50);
+		add_filter('the_seo_framework_sitemap_exclude_ids', [$this, 'excludeDashboard'], 8, 1);
+	}
+		public function handleRedirects():void
+		{
+			if (!is_singular(BASE.'dash') && !is_post_type_archive(BASE.'dash') && !is_404()) {
+				return;
+			}
+
+
+			if (!is_404()) {
+				if (!is_user_logged_in()) {
+					error_log('Redirecting to login - user not logged in');
+					$this->redirectToLogin();
+				} elseif (!isOurPeople() && !current_user_can('manage_options')) {
+					$this->redirectToHome();
+				} else {
+					$page = $this->getCurrentPageSlug();
+					if (array_key_exists($page, ['', 'dash'])) {
+						return;
+					}
+					if (!array_key_exists($page, $this->pages)) {
+						error_log('[DashboardManager]::handleRedirect could not find page for '.$page);
+						return;
+					}
+					$permission = $this->pages[$page]->getPermission();
+					if (!empty($permission) && !current_user_can($permission)) {
+						error_log('[DashboardManager]::handleRedirect User cannot manage '.$page);
+						$this->redirectToDashboard();
+					}
+				}
+			}
+			global $wp;
+			if (str_starts_with($wp->request, 'dash/') || $wp->request === 'dash') {
+				error_log('404 on dashboard URL, redirecting to dashboard home');
+				$this->redirectToDashboard();
+			}
+		}
+		public function dashboardTemplates(string $template):string
+		{
+			if (!is_singular(BASE.'dash') && !is_post_type_archive(BASE.'dash')) {
+				return $template;
+			}
+			$page = $this->getCurrentPage();
+			if (!empty($page->getIcon())) {
+				add_filter('jvbLoadingIcon', $page->getIcon());
+			}
+			ob_start();
+			jvbInlineStyles('nav');
+			jvbInlineStyles('dash');
+			jvbInlineStyles('forms');
+			$this->renderHeader();
+			$page->render();
+			$this->renderFooter();
+			return ob_get_clean();
+		}
+			protected function renderHeader():void
+			{
+				$page = $this->getCurrentPage();
+				?>
+				<!DOCTYPE html>
+			<html <?php language_attributes(); ?>>
+				<head>
+					<title><?= $page->getTitle() . ' | Dashboard' ?></title>
+					<meta charset="<?php bloginfo('charset'); ?>">
+					<meta name="viewport" content="width=device-width, initial-scale=1.0">
+					<?php
+					foreach ($this->pages as $page) {
+						if (empty($page->getPermission()) || current_user_can($page->getPermission())) {
+							echo '<link rel="preconnect" href="'.$page->getURL().'">';
+						}
+					}
+					?>
+					<link rel="preconnect" href="<?= get_home_url()?>"/>
+					<?php wp_head(); ?>
+				</head>
+			<body class="dashboard<?= ' '.$this->getCurrentPageSlug()?>">
+				<?php jvbAccessibility();?>
+				<header>
+					<?= jvbDarkModeToggle() ?>
+					<?php
+					$function = BASE.'render_core_site_logo';
+					if (function_exists($function)) {
+						echo $function([],'');
+					} else {
+						echo render_block( [
+							'blockName' => 'core/site-logo',
+							'attrs'     => [],
+						]);
+					}
+					?>
+
+					<nav>
+						<ul>
+							<?= jvbNotificationMenu() ?>
+							<?= jvbHelpMenu() ?>
+							<li><a href="<?=wp_logout_url(get_home_url())?>" title="Logout"><?=jvbIcon('sign-out')?></a></li>
+						</ul>
+					</nav>
+				</header>
+
+				<main><section class="replace">
+				<?php
+			}
+
+			protected function renderFooter():void
+			{
+				 ?>
+				</section>
+
+				<?= $this->outputSidebarNavigation(); ?>
+
+				<footer class="col">
+					<?= jvbLoadingScreen() ?>
+					<?= TaxonomySelector::outputSelectorModal() ?>
+		<!--            <nav class="dashboard-nav">-->
+						<?php
+		//                $current_page = $this->getCurrentPageSlug();
+		//                $pages = $this->getUserAllowedPages()?:[];
+		//                echo '<ul>';
+		//                foreach ($pages as $slug => $page) {
+		//					$slug = $this->getSlug($slug, $page);
+		//					$icon = $this->getIcon($slug, $page);
+		//					// Add data-page attribute for the navigator
+		//                    $active = ($current_page == $slug) ? ' class="current"' : '';
+		//                    $current = ($current_page == $slug) ? ' aria-current="page"' : '';
+		//
+		//
+		//					$link = ($page === 'dash') ? '/'.$page : "/dash/$slug";
+		//                    printf(
+		//                        '<li%s><a href="%s"%s data-page="%s" data-dash title="%s">%s<span>%s</span></a></li>',
+		//                        $active,
+		//                        get_home_url(null, $link),
+		//                        $current,
+		//                        $slug,
+		//                        $page,
+		//                        jvbDashIcon($icon, ['title'=> $page]),
+		//                        $page
+		//                    );
+		//                }
+		//
+		//                echo '</ul>';
+						?>
+		<!--            </nav>-->
+				</footer>
+
+
+				<?php
+				do_action('jvbRenderDashboardSettings', $this->getCurrentPageSlug());
+				?>
+				<?php wp_footer(); ?>
+
+				</body>
+				</html>
+
+				<?php
+			}
+
+			protected function outputSidebarNavigation():string
+			{
+				$menu = new Navigation('sidebar');
+				$menuClasses = ['left'];
+				$itemClasses = ['col'];
+				$menu->addClass('sidebar left')->hasToggle()->defaultMenuClasses($menuClasses);
+				$menu->defaultItemClasses($itemClasses);
+
+				//Add the Main Dashboard first
+				$dashboard = $menu->addItem('Dashboard', jvbDashIcon($this->pages['dash']->getIcon()))
+					->url($this->pages['dash']->getURL());
+
+				foreach ($this->sections as $section) {
+					$pages = array_filter($this->pages, function($page) use ($section) {
+						$canDo = empty($page->getPermission())  ||  current_user_can($page->getPermission());
+						return $page->getSection() === $section && $canDo;
+					});
+
+					if (empty($pages)) {
+						continue;
+					}
+					$icon = !empty($section['icon']??'') ? jvbDashIcon($section['icon']) : null;
+					$section = $menu->addItem($section['title'], $icon)
+						->submenu($section['slug'])
+						->defaultMenuClasses($menuClasses)
+						->defaultItemClasses($itemClasses);
+					foreach ($pages as $page) {
+						$icon = empty($page->getIcon()) ? null : jvbDashIcon($page->getIcon());
+						$item = $section->addItem($page->getTitle(), $icon)
+							->url($page->getURL());
+						$registrar = Registrar::getInstance($page->getSlug());
+						if ($registrar && !empty($registrar->registrar->taxonomies)) {
+							$itemMenu = $item->submenu($page->getSlug());
+							foreach ($registrar->registrar->taxonomies as $taxonomy) {
+								$taxonomy = jvbNoBase($taxonomy);
+								if (!array_key_exists($taxonomy, $this->pages)) {
+									error_log('Could not add Taxonomy subpage for '.$taxonomy);
+									continue;
+								}
+								$icon = empty($this->pages[$taxonomy]->getIcon()) ? null : jvbDashIcon($this->pages[$taxonomy]->getIcon());
+								$itemMenu->addItem($this->pages[$taxonomy]->getTitle(),$icon)
+									->url($this->pages[$taxonomy]->getURL());
+							}
+						}
+					}
+
+				}
+				$pages = $this->getUserAllowedPages()?:[];
+				//Dashboard
+					//Referrals
+				$dashboard = $menu->addItem('Dashboard',jvbDashIcon('door'))
+					->url($this->baseURL);
+		//			->submenu('dashboard')
+		//			->defaultMenuClasses($menuClasses)
+		//			->defaultItemClasses($itemClasses);
+				//notifications
+				if (in_array('Notifications', $pages)) {
+					$menu->addItem('Notifications',jvbDashIcon('bell'))
+						->url($this->baseURL.'/notifications');
+				}
+				if (in_array('Referrals', $pages)) {
+					$menu->addItem('Referrals', jvbDashIcon('hand-heart'))
+						->url($this->baseURL.'/referrals');
+				}
+				if (in_array('Favourites', $pages)) {
+					$menu->addItem('Favourites', jvbDashIcon('heart'))
+						->url($this->baseURL.'/favourites');
+				}
+
+				//Content
+					//content types
+				$all = array_merge(
+					Registrar::getRegistered('post'),
+					Registrar::withFeature('is_content', 'term')
+				);
+				$availableContent = [];
+//				$availableContent = array_filter($pages, function($page, $key) use($all) {
+//					return !is_numeric($key) && in_array($key, $all) && JVB()->roles()->checkRole($this->user, $key);
+//				}, ARRAY_FILTER_USE_BOTH);
+				if (!empty ($availableContent)){
+					$content = $menu->addItem('Your Content', jvbDashIcon('book-bookmark'))
+						->submenu('content')
+						->defaultMenuClasses($menuClasses)
+						->defaultItemClasses($itemClasses);
+					foreach ($availableContent as $slug => $page) {
+						$registrar = Registrar::getInstance($slug);
+
+						$item = $content->addItem($page, $registrar->getIcon())
+							->url($this->baseURL.'/'.$slug);
+
+						if ($registrar->getType() === 'post') {
+							$taxonomies = $registrar->registrar->taxonomies;
+							if (!empty ($taxonomies)) {
+								//TODO: If we add a dedicated 'create item' page, remove this from the empty check
+								$itemMenu = $item->submenu($slug);
+								foreach ($taxonomies as $s) {
+									$taxRegistrar = Registrar::getInstance($s);
+									if ($taxRegistrar) {
+										$itemMenu->addItem($taxRegistrar->getPlural(), $taxRegistrar->getIcon())
+										->url($this->baseURL.'/'.$s);
+									}
+
+								}
+							}
+						}
+
+
+					}
+				}
+
+				//Taxonomies
+
+				//Settings
+				$settings = $menu->addItem('Settings', jvbDashIcon('faders'))
+					->submenu('settings')
+					->defaultItemClasses($itemClasses)
+					->defaultMenuClasses($menuClasses);
+
+					//SEO
+					if (in_array('SEO', $pages)) {
+						$settings->addItem('SEO', jvbDashIcon('robot'))
+							->url($this->baseURL.'/seo');
+					}
+					//Integrations
+					if (in_array('Integrations', $pages)) {
+						$settings->addItem('Integrations', jvbDashIcon('plugs-connected'))
+							->url($this->baseURL.'/integrations');
+					}
+				//Account
+				$account = $menu->addItem('Account', jvbDashIcon('user-circle'))
+					->url($this->baseURL.'/account')
+					->submenu('account')
+					->defaultMenuClasses($menuClasses)
+					->defaultItemClasses($itemClasses);
+				$account->addItem('Reset Password', jvbDashIcon('password'))
+					->url($this->baseURL.'/reset-password');
+					//name + contact
+					//reset password
+
+					if (in_array('notifications', $pages)) {
+						$account->addItem('Permissions', jvbDashIcon('keyhole'))
+							->url($this->baseURL.'/permissions');
+					}
+
+				echo $menu->render();
+			}
+		public function redirectFromAdmin():void
+		{
+			//Skip if already processing a redirect
+			if (defined('DOING_AJAX') && DOING_AJAX) {
+				return;
+			}
+
+			if (current_user_can('manage_options')) {
+				return;
+			}
+			//Redirect to custom dashboard
+			if (is_user_logged_in() && isOurPeople()) {
+				$this->redirectToDashboard();
+			}
+		}
+		public function dashboardScripts():void
+		{
+
+		}
+
+		public function excludeDashboard(array $IDs):array {
+			$exclude = $this->cache->remember(
+				'dashboardIDs',
+				function() {
+					return get_posts([
+						'post_type'	=> BASE.'dash',
+						'posts_per_page' => -1,
+						'fields' => 'ids',
+					]);
+				});
+			if (!empty($exclude)) {
+				$IDs = array_merge($IDs, $exclude);
+			}
+
+			return $IDs;
+		}
+
+	private function buildPages():void
+	{
+		$this->addPage('dash', 'Dashboard','door');
+		$this->buildContentPages();
+		$this->buildReferrals();
+		$this->buildMembership();
+		$this->buildFavourites();
+		$this->buildKarma();
+		$this->buildNotifications();
+		$this->buildIntegrations();
+		$this->buildSettingsPages();
+		$this->buildAccountPages();
+	}
+
+		private function buildContentPages():void
+		{
+			$content = Registrar::getRegistered('post');
+			foreach ($content as $c) {
+				$registrar = Registrar::getInstance($c);
+				$page = $this->addPage($registrar->getPlural(), $c, $registrar->getIcon());
+				$page->setPermission(RoleManager::getPermissionName('edit', $c));
+				$page->setSection('content');
+				$this->pages[$c] = $page;
+			}
+		}
+
+		private function buildSettingsPages():void
+		{
+			$seo = $this->addPage('SEO', 'seo', 'robot');
+			$seo->setSection('settings');
+			$seo->setPermission('manage_options');
+			$this->pages['seo'] = $seo;
+
+		}
+		private function buildAccountPages():void
+		{
+			if (Site::has('support')) {
+				$page = $this->addPage('Support', 'support', 'question');
+				$page->setSection('support');
+			}
+
+			$account = $this->addPage('Account', 'account', 'user-circle');
+			$account->setSection('account');
+			$accountID = $account->getID();
+
+			$password = $this->addPage('Reset Password', 'reset-password', 'password', $accountID);
+			$password->setOrder(2);
+			$password->setSection('account');
+
+		}
+		private function buildReferrals():void
+		{
+			if (Site::has('referrals')) {
+				$page = $this->addPage('Referrals', 'referrals', 'hand-heart');
+			}
+		}
+		private function buildMembership():void
+		{
+			$membership = Site::membership();
+			if ($membership) {
+				if ($membership->has('can_invite')) {
+					$page = $this->addPage('Invite', 'invite', '');
+					$page->setSection('notifications');
+				}
+				if ($membership->has('term_approval')) {
+					$page = $this->addPage('Approvals', 'approvals', 'check-circle');
+					$page->setSection('notifications');
+				}
+				if ($membership->has('forum')) {
+					$page = $this->addPage('Forum', 'forum', 'chats-teardrop');
+				}
+				if ($membership->has('member_content')) {
+					$page = $this->addPage('Metrics', 'metrics', 'chart-line');
+				}
+			}
+		}
+		private function buildFavourites():void
+		{
+			if (Site::has('favourites')) {
+				$page = $this->addPage('Favourites', 'favourites', 'heart');
+				//TODO: Lists, Share permissions
+			}
+		}
+		private function buildKarma():void
+		{
+			if (!empty(Registrar::withFeature('karma'))) {
+				$page = $this->addPage('Karmic', 'karmic', 'arrow-fat-up');
+			}
+		}
+		private function buildNotifications():void
+		{
+			if (Site::has('notifications')) {
+				$page = $this->addPage('Notifications', 'notifications', 'bell');
+				$page->setSection('notifications');
+
+				$page = $this->addPage('Permissions', 'permissions', 'gear-six');
+				$page->setSection('notifications');
+				$page->setOrder(999);
+			}
+		}
+		private function buildIntegrations():void
+		{
+			if (Site::hasAnyIntegration()) {
+				$page = $this->addPage('Integrations', 'integrations', 'plugs-connected');
+				$page->setSection('settings');
+				$parent = $page->getID();
+				foreach (array_keys(Site::getIntegrations()) as $integration) {
+					$integration = match($integration) {
+						'maps' => 'JVBase\integrations\GoogleMaps',
+						'square' => 'JVBase\integrations\Square',
+						'facebook' => 'JVBase\integrations\Facebook',
+						'helcim' => 'JVBase\integrations\Helcim',
+						'instagram' => 'JVBase\integrations\Instagram',
+						'bluesky' => 'JVBase\integrations\BlueSky',
+						'gmb' => 'JVBase\integrations\GoogleMyBusiness',
+						'cloudflare' => 'JVBase\integrations\Cloudflare',
+						'umami' => 'JVBase\integrations\Umami',
+						'postmark' => 'JVBase\integrations\PostMark',
+					};
+					$title = $integration::title();
+					$icon = $integration::icon();
+					$page = $this->addPage($title, $title, $icon, $parent);
+				}
+			}
+		}
+
+	public function addPage(string $title, string $slug = '', string $icon = '', int $parent = 0):DashboardPage
+	{
+		$page = new DashboardPage($title, $slug, $icon, $parent);
+		$this->pages[$page->getSlug()] = $page;
+		return $page;
+	}
+
+	protected function buildSections():void
+	{
+		$sections = array_values(array_filter(array_unique(array_map(function ($page) {
+			return $page->getSection();
+		}, $this->pages))));
+		foreach ($sections as $section) {
+			$isLink = false;
+			switch ($section) {
+				case 'content':
+					$title = 'Your Content';
+					$icon = 'book-bookmark';
+					break;
+				case 'settings':
+					$title = 'Settings';
+					$icon = 'faders';
+					break;
+				case 'account':
+					$title = 'Account';
+					$icon = 'user-circle';
+					break;
+				case 'notifications':
+					$title = 'Notifications';
+					$icon = 'bell';
+					break;
+				case 'support':
+					$title = 'Support';
+					$icon = 'question';
+					break;
+				default:
+					$mainPage = $this->getMainPage($section);
+					if ($mainPage) {
+						$title = $mainPage->getTitle();
+						$icon = $mainPage->getIcon();
+						$isLink = true;
+					} else {
+						error_log('[DashboardManager]::buildSections Could not create section for '.$section);
+						return;
+					}
+					break;
+			}
+
+			if (!$isLink && $this->hasMainPage($section)) {
+				$isLink = true;
+			}
+
+			$this->sections[$section] = new Section($title, $section, $icon);
+			if ($isLink) {
+				$this->sections[$section]->setIsLink(true);
+			}
+		}
+	}
+		protected function getSectionPages(string $section):array
+		{
+			return array_filter($this->pages, function($page) use ($section) {
+				return $page->getSection() === $section;
+			});
+		}
+
+		protected function getMainPage(string $section):DashboardPage|false
+		{
+			$sectionPages = $this->getSectionPages($section);
+			$mainPage = array_filter($sectionPages, function ($page) use ($section) {
+				return $page->getSlug() === $section;
+			});
+
+			return empty($mainPage) ? false : $mainPage[0];
+		}
+
+		protected function hasMainPage(string $section):bool
+		{
+			return $this->getMainPage($section) !== false;
+		}
+
+	public function addSection(string $title, ?string $slug = null, string $icon = '', ?string $parent = null):void
+	{
+		$section = new Section($title, $slug, $icon, $parent);
+		$this->sections[$section->getSlug()] = $section;
+	}
+	public function addPageToSection(string $slug, ?string $section = null):bool
+	{
+		if (!array_key_exists($slug, $this->pages)) {
+			error_log('[DashboardManager]::addPageToSection Attempted to add page to section that doesn\'t exist: '.$slug);
+			return false;
+		}
+		if (!is_null($section) && !array_key_exists($section, $this->sections)) {
+			error_log('[DashboardManager]::addPageToSection Please configure section first for '.$section);
+			return false;
+		}
+		$this->pages[$slug]->setSection($section);
+		return true;
+	}
+
+
+	#[NoReturn]protected function redirectToLogin():void
+	{
+		wp_redirect(wp_login_url(get_home_url(null, '/dash')));
+		exit;
+	}
+
+	#[NoReturn]protected function redirectToDashboard():void
+	{
+		wp_redirect(get_home_url(null, '/dash'));
+		exit;
+	}
+	#[NoReturn]protected function redirectToHome():void
+	{
+		wp_redirect(get_home_url());
+		exit;
+	}
+
+	protected function getCurrentPage():DashboardPage|false
+	{
+		$slug = $this->getCurrentPageSlug();
+		if (!array_key_exists($slug, $this->pages)) {
+			error_log('[DashboardManager]::getCurrentPage Could not get configuration for '.$slug);
+			return false;
+		}
+		return $this->pages[$slug];
+	}
+
+	protected function getCurrentPageSlug():string
+	{
+		if (is_post_type_archive(BASE.'dash')) {
+			return 'dash';
+		}
+
+		global $post;
+		if (!$post) {
+			return '';
+		}
+
+		return $post->post_name;
+	}
+}
diff --git a/inc/managers/Dashboard/DashboardPage.php b/inc/managers/Dashboard/DashboardPage.php
new file mode 100644
index 0000000..653c86d
--- /dev/null
+++ b/inc/managers/Dashboard/DashboardPage.php
@@ -0,0 +1,159 @@
+<?php
+namespace JVBase\managers\Dashboard;
+
+use JVBase\managers\Cache;
+use WP_Query;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class DashboardPage {
+	protected string $title;
+	protected string $slug;
+	protected string $icon;
+	protected string $URL;
+	protected int $ID;
+	protected ?string $permission = null;
+	protected ?string $section = null;
+	protected array $scripts = [];
+	protected int $order = 0;
+	protected Cache $cache;
+
+	public function __construct(string $title, string $slug = '', string $icon ='', int $parent = 0) {
+		$this->cache = Cache::for('dashboard');
+		$this->title = $title;
+		if (empty($slug)) {
+			$this->setSlug($title);
+		}else {
+			$this->setSlug($slug);
+		}
+		$this->icon = $icon;
+		$this->setID($parent);
+		$this->setURL();
+	}
+
+	public function setID(int $parentID):void
+	{
+		$this->ID = $this->cache->remember(
+			$this->slug.'_ID',
+			function() use ($parentID) {
+				$existing = new WP_Query([
+					'post_type'	=> BASE.'dash',
+					'name'		=> $this->slug,
+					'fields'	=> 'ids',
+					'posts_per_page'=> 1,
+				]);
+				if ($existing->have_posts()) {
+					return $existing->posts[0];
+				}
+
+				$args = [
+					'post_title'	=> $this->title,
+					'post_name'		=> $this->slug,
+					'post_type'		=> BASE.'dash',
+					'post_status'	=> 'publish'
+				];
+				if ($parentID > 0) {
+					$args['post_parent'] = $parentID;
+				}
+				return wp_insert_post($args);
+			}
+		);
+	}
+	public function getID():int
+	{
+		return $this->ID;
+	}
+
+	public function setURL():void
+	{
+		$this->URL = $this->cache->remember(
+			$this->slug.'_url',
+			function () {
+				return get_permalink($this->ID);
+			}
+		);
+	}
+
+	public function getURL():string
+	{
+		return $this->URL;
+	}
+
+
+	public function setTitle(string $title):void
+	{
+		$this->title = $title;
+	}
+	public function getTitle():string
+	{
+		return $this->title;
+	}
+
+	public function setSlug(string $slug):void
+	{
+		$this->slug = self::sanitizeString($slug);
+	}
+	public function getSlug():string
+	{
+		return $this->slug;
+	}
+
+	public function setIcon(string $icon):void
+	{
+		$this->icon = $icon;
+	}
+	public function getIcon():string
+	{
+		return $this->icon;
+	}
+
+	public function setPermission(string $permission):void
+	{
+		$this->permission = $permission;
+	}
+	public function getPermission():?string
+	{
+		return $this->permission;
+	}
+
+	public function setSection(string $section):void
+	{
+		$this->section = self::sanitizeString($section);
+	}
+	public function getSection():?string
+	{
+		return $this->section;
+	}
+
+	public function setOrder(int $order):void
+	{
+		$this->order = $order;
+	}
+	public function getOrder():int
+	{
+		return $this->order;
+	}
+
+	public function setRenderCallback(string $callback):void
+	{
+
+	}
+	public function render():string
+	{
+		return $this->cache->remember(
+			$this->ID,
+			function () {
+				return apply_filters(BASE.'render_'.$this->slug, '<h2>'.$this->title.' is not configured yet.</h2><p>Add a filter to '.BASE.'render_'.$this->slug.'</p>');
+			}
+		);
+	}
+	/*********************************************************
+	 * UTILITY
+	 ********************************************************/
+	public static function sanitizeString(string $string):string
+	{
+		return str_replace('_', '-', strtolower(sanitize_title($string)));
+	}
+}
diff --git a/inc/managers/Dashboard/Section.php b/inc/managers/Dashboard/Section.php
new file mode 100644
index 0000000..4df5287
--- /dev/null
+++ b/inc/managers/Dashboard/Section.php
@@ -0,0 +1,55 @@
+<?php
+namespace JVBase\managers\Dashboard;
+
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+class Section {
+	protected ?string $parent = null;
+	protected string $title;
+	protected string $slug;
+	protected string $icon = '';
+	protected int $order = 0;
+	protected bool $isLink = false;
+
+	public function __construct(string $title, ?string $slug = null, string $icon = '', ?string $parent = null) {
+		$this->title = $title;
+		$this->slug = is_null($slug) ? DashboardPage::sanitizeString($title) : DashboardPage::sanitizeString($slug);
+		$this->icon = $icon;
+		$this->parent = $parent;
+	}
+
+	public function getTitle():string
+	{
+		return $this->title;
+	}
+	public function getSlug():string
+	{
+		return $this->slug;
+	}
+	public function getIcon():string
+	{
+		return $this->icon;
+	}
+	public function getParent():?string
+	{
+		return $this->parent;
+	}
+	public function setOrder(int $order):void
+	{
+		$this->order = $order;
+	}
+	public function getOrder():int
+	{
+		return $this->order;
+	}
+	public function setIsLink(bool $isLink):void
+	{
+		$this->isLink = $isLink;
+	}
+	public function getIsLink():bool
+	{
+		return $this->isLink;
+	}
+}
diff --git a/inc/managers/Dashboard/_setup.php b/inc/managers/Dashboard/_setup.php
new file mode 100644
index 0000000..ebf6ef0
--- /dev/null
+++ b/inc/managers/Dashboard/_setup.php
@@ -0,0 +1,8 @@
+<?php
+if (!defined('ABSPATH')) {
+	exit;
+}
+
+require_once JVB_DIR . '/inc/managers/Dashboard/DashboardManager.php';
+require_once JVB_DIR . '/inc/managers/Dashboard/DashboardPage.php';
+require_once JVB_DIR . '/inc/managers/Dashboard/Section.php';
diff --git a/inc/managers/DashboardManager.php b/inc/managers/DashboardManager.php
index bb671e8..152afc6 100644
--- a/inc/managers/DashboardManager.php
+++ b/inc/managers/DashboardManager.php
@@ -1,7 +1,7 @@
 <?php
 namespace JVBase\managers;
 
-use JVBase\forms\TaxonomySelector;
+use JetBrains\PhpStorm\NoReturn;use JVBase\forms\TaxonomySelector;
 use JVBase\base\Site;
 use JVBase\meta\Form;
 use JVBase\registrar\Registrar;
@@ -111,7 +111,7 @@
     /**
      * Redirect all non-admin users from wp-admin to custom dashboard
      */
-    public function redirectFromAdmin()
+    public function redirectFromAdmin():void
     {
 		// Skip if already processing a redirect
 		if (defined('DOING_AJAX') && DOING_AJAX) {
@@ -133,13 +133,13 @@
 		}
     }
 
-	protected function redirectToLogin():void
+	#[NoReturn]protected function redirectToLogin():void
 	{
 		wp_redirect(wp_login_url(get_home_url(null, '/dash')));
 		exit;
 	}
 
-	protected function redirectToDashboard():void
+	#[NoReturn]protected function redirectToDashboard():void
 	{
 		wp_redirect(get_home_url(null, '/dash'));
 		exit;
@@ -191,7 +191,6 @@
 		if (!is_404() && !is_user_logged_in()) {
 			error_log('Redirecting to login - user not logged in');
 			$this->redirectToLogin();
-			return;
 		}
 
 		// If logged in but doesn't have dashboard access, redirect to home
@@ -206,7 +205,6 @@
 		if (is_404() && (str_starts_with($wp->request, 'dash/') || $wp->request === 'dash')) {
 			error_log('404 on dashboard URL, redirecting to dashboard home');
 			$this->redirectToDashboard();
-			return;
 		}
 
 		// For valid dashboard pages, check access permissions
@@ -395,7 +393,6 @@
 		$this->renderHeader();
 		// Pass to page handler
 		$constantSlug = $this->getConstantSlug($page);
-
         echo apply_filters(
 			'jvbDashboardPage',
 			$this->renderPage($page),
diff --git a/inc/managers/RoleManager.php b/inc/managers/RoleManager.php
index a22b585..e859813 100644
--- a/inc/managers/RoleManager.php
+++ b/inc/managers/RoleManager.php
@@ -441,9 +441,9 @@
 		$content = jvbNoBase($content);
 		$registrar = Registrar::getInstance($content);
 		if ($registrar && $registrar->getPlural()) {
-			return str_replace(' ', '_', $registrar->getPlural());
+			return strtolower(str_replace(' ', '_', $registrar->getPlural()));
 		}
-		return str_replace(' ', '_', $content.'s');
+		return strtolower(str_replace(' ', '_', $content.'s'));
 	}
 
 	public static function activate(): void
@@ -821,6 +821,18 @@
 		}
 		return null;
 	}
+	public static function getPermissionName(string $action, string $content, ?int $ID = null):?string
+	{
+		$plural = (new self())->getContentPlural($content);
+		switch ($action) {
+			case 'edit':
+				if ($ID) {
+					return "edit_{$content}";
+				}
+				return "edit_{$plural}";
+		}
+		return null;
+	}
 
 	public function maybeSwitchPermissions(int $object_id, array $terms, array $tt_ids, string $taxonomy, bool $append, array $old_tt_ids):void
 	{
diff --git a/inc/managers/_setup.php b/inc/managers/_setup.php
index d55098a..379605d 100644
--- a/inc/managers/_setup.php
+++ b/inc/managers/_setup.php
@@ -10,11 +10,10 @@
 require(JVB_DIR . '/inc/managers/CustomTable.php');
 //require(JVB_DIR . '/inc/managers/CacheManager.php');
 require(JVB_DIR . '/inc/managers/Cache.php');
-class_alias('JVBase\managers\Cache', 'JVBase\managers\CacheManager');
 
 
 require(JVB_DIR . '/inc/managers/IconsManager.php');
-add_action('init', 'jvbInit', 1); // Priority 1 - very early
+add_action('init', 'jvbInit', 0); // Priority 1 - very early
 function jvbInit(): void
 {
 
@@ -37,6 +36,7 @@
 	}
 
 	if (Site::has('dashboard')) {
+		require(JVB_DIR . '/inc/managers/DashboardManager.php');
 		require(JVB_DIR . '/inc/managers/CRUDManager.php');
 		require(JVB_DIR . '/inc/managers/UploadManager.php');
 	}
@@ -75,10 +75,6 @@
 		require(JVB_DIR . '/inc/managers/DirectoryManager.php');
 	}
 
-	if (Site::has('dashboard')) {
-		require(JVB_DIR . '/inc/managers/DashboardManager.php');
-	}
-
 	if (Site::has('referrals')) {
 		require(JVB_DIR . '/inc/managers/ReferralManager.php');
 	}
diff --git a/jvb.php b/jvb.php
index 6cc5ff8..2e188ad 100644
--- a/jvb.php
+++ b/jvb.php
@@ -260,7 +260,7 @@
 add_action('plugins_loaded', 'jvb_registrar_definitions',2);
 add_action('plugins_loaded', 'jvb_field_definitions', 3);
 add_action('plugins_loaded', 'jvb_options_definitions',3);
-add_action('init', 'jvbLoadBase', 1);
+add_action('init', 'jvbLoadBase', 2);
 add_action('init', 'jvb_integration_definitions',3);
 add_action('init', 'jvb_field_section_definitions', 5);
 /**

--
Gitblit v1.10.0