From ac444cba221832c012c0435fdc8339fe9f37febb Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Mon, 11 May 2026 18:35:04 +0000
Subject: [PATCH] =Some changes to the CRUD.js editing, timeline post configuration

---
 inc/managers/DashboardManager.php | 1365 +++++++++++++++++++++++++++++++++++++++++++----------------
 1 files changed, 994 insertions(+), 371 deletions(-)

diff --git a/inc/managers/DashboardManager.php b/inc/managers/DashboardManager.php
index e15be50..062c1bf 100644
--- a/inc/managers/DashboardManager.php
+++ b/inc/managers/DashboardManager.php
@@ -1,9 +1,12 @@
 <?php
 namespace JVBase\managers;
 
-use JVBase\managers\CRUD;
-use JVBase\meta\MetaManager;
-use WP_User;
+use JVBase\forms\TaxonomySelector;
+use JVBase\base\Site;
+use JVBase\meta\Form;
+use JVBase\registrar\Registrar;
+use JVBase\ui\Navigation;
+use WP_Error;use WP_Query;use WP_User;
 
 if (!defined('ABSPATH')) {
     exit; // Exit if accessed directly
@@ -15,27 +18,45 @@
 class DashboardManager
 {
     protected WP_User $user;
-    protected CacheManager $cache;
+    protected Cache $cache;
     protected string $role;
+	protected string $baseURL;
     protected int $userLink;
 
     public function __construct()
     {
-        $this->cache = new CacheManager('dashboard');
-		$this->cache->invalidateGroup('dashboard');
+        $this->cache = Cache::for('dashboard', WEEK_IN_SECONDS)->connect('user');
         add_action('init', [$this, 'registerDashboard']);
-        if (!$this->isRegistered()) {
-            add_action('init', [$this, 'buildDashboard']);
-        }
+		$this->cache->flush();
         $this->user = wp_get_current_user();
-        $this->role = jvbUserRole();
-        $this->userLink = (int)get_user_meta($this->user->ID, BASE.'link', true);
+        $this->role = jvbUserRole($this->user->ID);
+        $this->userLink = (int)get_user_meta($this->user->ID, BASE.'profile_link', true);
+		$this->baseURL = get_home_url(null, '/dash');
 
+    	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('jvbDashboardPage', [$this, 'renderIndex'], 10, 2);
+
+		jvb_register_do_once('buildDashboard', [$this, 'activate']);
+
+		add_filter('the_seo_framework_sitemap_exclude_ids', [$this, 'excludeDashboard'], 10, 1);
     }
 
+	public function excludeDashboard(array $ids):array {
+		$cached = $this->cache->remember(
+			'dashboardIDs',
+			function() {
+				return get_posts([
+					'post_type'	=> BASE.'dash',
+					'posts_per_page' => -1,
+					'fields' => 'ids',
+				]);
+			});
+		return array_merge($ids, $cached);
+	}
+
     /**
      * Registers the custom post type that handles the dashboard
      * @return void
@@ -47,18 +68,21 @@
         $singular = 'Dashboard';
         register_post_type(BASE.'dash', array(
             'labels'                => [
-                'name' => $plural,
-                'singular_name' => $singular,
-                'menu_name' => $plural,
-                'add_new' => "Add New {$singular}",
-                'add_new_item' => "Add New {$singular}",
-                'edit_item' => "Edit {$singular}",
-                'new_item' => "New {$singular}",
-                'view_item' => "View {$singular}",
-                'search_items' => "Search {$plural}",
-                'not_found' => "No {$plural} found",
-                'not_found_in_trash' => "No {$plural} found in Trash"
-            ],
+				'name'               => $plural,
+				'singular_name'      => $singular,
+				'menu_name'          => $plural,
+				'name_admin_bar'     => $singular,
+				'add_new'            => "Add New",
+				'add_new_item'       => "Add New {$singular}",
+				'new_item'           => "New {$singular}",
+				'edit_item'          => "Edit {$singular}",
+				'view_item'          => "View {$singular}",
+				'all_items'          => "All {$plural}",
+				'search_items'       => "Search {$plural}",
+				'parent_item_colon'  => "Parent {$plural}:",
+				'not_found'          => "No {$plural} found.",
+				'not_found_in_trash' => "No {$plural} found in Trash.",
+			],
             'menu_icon'             => jvbCSSIcon('gauge'),
             'public'                => true,
             'publicly_queryable'    => true,
@@ -80,89 +104,210 @@
      */
     public function redirectFromAdmin()
     {
+		// Skip if already processing a redirect
+		if (defined('DOING_AJAX') && DOING_AJAX) {
+			return;
+		}
+
+		// Ensure user is fully loaded
+		if (!did_action('wp_loaded')) {
+			return;
+		}
+
         // Allow admins to access wp-admin if needed
         if (current_user_can('manage_options')) {
             return;
         }
-
         // Redirect to custom dashboard
-        wp_redirect(home_url('dash'));
-        exit;
+       	if (is_user_logged_in() && isOurPeople()) {
+			$this->redirectToDashboard();
+		}
     }
 
+	protected function redirectToLogin():void
+	{
+		wp_redirect(wp_login_url(get_home_url(null, '/dash')));
+		exit;
+	}
+
+	protected function redirectToDashboard():void
+	{
+		wp_redirect(get_home_url(null, '/dash'));
+		exit;
+	}
+
+
+	protected function getConfig(string $page):Registrar|false
+	{
+		$pages = $this->getAllDashboardPages();
+		$key = array_search($page, $pages);
+		if ($key === false || is_numeric($key)) {
+			return false;
+		}
+		return Registrar::getInstance($key)??false;
+	}
+
+	/**
+	 * Check if user can access page and redirect if not
+	 * @param string $page
+	 * @param int|null $userID
+	 * @return void
+	 */
+	protected function requirePageAccess(string $page, ?int $userID = null):void
+	{
+		$allowedPages = $this->getUserAllowedPages($userID);
+
+		if (!in_array($page, $allowedPages)) {
+			$this->redirectToDashboard();
+		}
+	}
+
+	protected function getTitle(string $slug):string
+	{
+		$registrar = $this->getConfig($slug);
+		if ($registrar) {
+			return $registrar->getConfig('dashboard')['title']??$registrar->getPlural();
+		}
+		return ucwords(str_replace('-', ' ', str_replace('_', ' ', $slug)));
+	}
+
+	public function handleRedirects():void
+	{
+		// Only process dashboard-related pages and 404s
+		if (!is_singular(BASE.'dash') && !is_post_type_archive(BASE.'dash') && !is_404()) {
+			return;
+		}
+
+		// Check if user is logged in first
+		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
+		if (!is_404() && !isOurPeople() && !current_user_can('manage_options')) {
+			error_log('Redirecting to home - user lacks dashboard access');
+			wp_redirect(home_url());
+			exit;
+		}
+
+		// Handle 404s that are trying to access dashboard URLs
+		global $wp;
+		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
+		if (!is_404()) {
+			$page = $this->getCurrentPageSlug();
+
+			// Dashboard home is always accessible (if authenticated)
+			if ($page === '' || $page === 'dash') {
+				return;
+			}
+			$page = $this->getCurrentPageTitle();
+			// Check if page exists in allowed pages
+			$allowedPages = $this->getUserAllowedPages();
+
+			if (!in_array($page, $allowedPages)) {
+				error_log("User not allowed to access page: {$page}");
+				$this->redirectToDashboard();
+			}
+		}
+	}
+
     /**
-     * Ensures the necessary pages ar created
+     * Ensures the necessary pages are created
      * @return void
      */
-    public function buildDashboard():void
+    public function activate():void
     {
-        $manageableContent = jvbGetAllDashboardPages();
-        foreach ($manageableContent as $slug) {
-            $title = $this->getTitle($slug);
-			$slug = sanitize_title($title);
+        $manageableContent = $this->getAllDashboardPages();
+		error_log('[DashboardManager]::buildDashboard Manageable Content: '.print_r($manageableContent, true));
+        foreach ($manageableContent as $slug => $page) {
+			if ($page === 'dash') {
+				continue;
+			}
 
-            $ID = wp_insert_post(array(
-                'post_title'    => $title,
+			$ID = $this->createDashboardPage($slug, $page);
+
+
+			$registrar = Registrar::getInstance($slug);
+			if ($registrar) {
+				$create = [
+					'new_'	=> 'Create New ',
+					'edit_'	=> 'Edit '
+				];
+				$parentID = (int)$ID;
+				foreach ($create as $s => $t) {
+					$s .= $slug;
+					$t .= $page;
+					$this->createDashboardPage($s, $t, $parentID);
+				}
+			}
+			if ($page === 'Integrations') {
+				$this->buildIntegrationPages($ID);
+			}
+        }
+    }
+		public function createDashboardPage(string $slug, string $page, int $parentID = 0):int|WP_Error
+		{
+			if (is_numeric($slug)) {
+				$slug = $this->getSlug($slug, $page);
+			}
+
+			$existing = new WP_Query([
+				'post_type'	=> BASE.'dash',
+				'name'	=> $slug,
+				'fields'	=> 'ids',
+				'posts_per_page'	=> 1,
+			]);
+
+			if ($existing->have_posts()) {
+				return $existing->posts[0];
+			}
+
+
+			$args = [
+				'post_title'    => $page,
                 'post_name'     => $slug,
                 'post_type'     => BASE.'dash',
                 'post_status'   => 'publish',
-            ));
-
-			if ($title === 'Integrations') {
-				$integrations = ['BlueSky', 'Cloudflare', 'Facebook', 'Google Maps', 'Google My Business', 'Helcim', 'Instagram', 'Square', 'Umami'];
-				foreach ($integrations as $integration) {
-					$slug = sanitize_title($integration);
-					wp_insert_post([
-						'post_title'    => $integration,
-						'post_name'		=> $slug,
-						'post_type'		=> BASE.'dash',
-						'post_status'	=> 'publish',
-						'post_parent'	=> $ID
-					]);
-				}
+			];
+			if ($parentID > 0) {
+				$args['post_parent'] = $parentID;
 			}
-        }
-        update_option(BASE.'dashboard_registered', true);
-        remove_action('init', [$this, 'buildDashboard']);
-    }
+            return wp_insert_post($args);
+		}
 
-    protected function getTitle(string $page):string
-    {
-        $content = JVB_CONTENT;
-        $contentTax = array_filter(JVB_TAXONOMY, function ($tax) {
-            return jvbCheck('is_content', $tax);
-        });
-        $content = array_merge($content, $contentTax);
-        $title = '';
+	/**
+	 * Build integration sub-pages
+	 * @param int $parentID
+	 * @return void
+	 */
+	protected function buildIntegrationPages(int $parentID):void
+	{
+		$integrations = JVB()->getAvailableServices(false);
 
+		foreach ($integrations as $name => $integration) {
+			$title = $integration->getTitle();
 
-        if (array_key_exists($page, $content)) {
-            $config = $content[$page];
-            $title = (array_key_exists('dash_title', $config)) ? $config['dash_title'] : $config['plural'];
-        } else {
-            switch ($page) {
-                case 'admin':
-                    $title = 'Admin';
-                    break;
-                default:
-					$title = ucwords(str_replace('_', ' ', str_replace('-', ' ', $page)));
-            }
-        }
+			$slug = sanitize_title($title);
+			$this->createDashboardPage($slug, $title, $parentID);
+		}
+	}
 
-        return $title;
-    }
 
     protected function getDescription(string $page):string
     {
-        $content = JVB_CONTENT;
-        $contentTax = array_filter(JVB_TAXONOMY, function ($tax) {
-            return jvbCheck('is_content', $tax);
-        });
-        $content = array_merge($content, $contentTax);
-        if (array_key_exists($page, $content)) {
-            $config = $content[$page];
-            $description = (array_key_exists('dash_description', $config)) ? $config['dash_description'] : '';
+		$registrar = $this->getConfig($page);
+        if ($registrar) {
+            $description =  $registrar->getConfig('dashboard')['description']??'';
         } else {
+			$description = apply_filters('jvbDashboardDescription', $page);
             switch ($page) {
                 case 'approval':
                     $description = 'See your approval requests for term creation, joining shops, or joining edmonton.ink. You can also help shape the community by approving other\'s requests!';
@@ -185,14 +330,6 @@
         return $description;
     }
 
-    /**
-     * Checks if we've already created the need pages
-     * @return bool
-     */
-    protected function isRegistered():bool
-    {
-        return get_option(BASE.'dashboard_registered', false);
-    }
 
     /**
      * Hacking into the template_include to set our custom templates and protections
@@ -205,81 +342,175 @@
         if (!is_singular(BASE.'dash') && !is_post_type_archive(BASE.'dash')) {
             return $template;
         }
-        if (!isOurPeople() && !current_user_can('manage_options')) {
-            wp_redirect(wp_login_url(get_home_url(2, '/dash')));
-            exit;
-        }
 
         // Get current page/section
+        $page = $this->getCurrentPageTitle();
+		$registrar = $this->getConfig($page);
+		if($registrar) {
+			add_filter('jvbLoadingIcon', function() use ($registrar) {
+				return $registrar->getIcon();
+			});
+		}
+		$integrationSlugs = array_map(function($name) {
+			return sanitize_title(str_replace('_', '-', $name));
+		}, array_keys(JVB()->getAvailableServices(false)));
 
-        $page = $this->getCurrentPage();
+		// Check if this is an integration subpage
+		if (in_array($page, $integrationSlugs)) {
+			// Pass along to the Integrations template handler which knows to check for subpages
+			$page = 'integrations';
+		}
+//		echo $this->renderDashboard($page);
 
-		// Enqueue needed styles/scripts
+		echo $this->cache->remember(
+			$page,
+		 	function() use ($page) {
+				return $this->renderDashboard($page);
+		 	}
+		);
+
+		return '';
+    }
+
+	protected function getConstantSlug(string $page):string
+	{
+		$slug = array_search($page, $this->getAllDashboardPages());
+		return (is_numeric($slug)) ? '' : $slug;
+	}
+	protected function renderDashboard(string $page):string
+	{
+		ob_start();
 		jvbInlineStyles('nav');
 		jvbInlineStyles('dash');
 		jvbInlineStyles('forms');
-		$this->cache->delete($page);
-		echo $this->cache->remember(
+		$this->renderHeader();
+		// Pass to page handler
+		$constantSlug = $this->getConstantSlug($page);
+
+        echo apply_filters(
+			'jvbDashboardPage',
+			$this->renderPage($page),
 			$page,
-			function() use ($page) {
-				ob_start();
-				$this->renderHeader();
-
-				switch ($page) {
-					case 'dash':
-						if (current_user_can('manage_options')) {
-							$content = apply_filters('jvbAdminDashboard', '');
-
-							if ($content !== '') {
-								echo $content;
-							}else {
-								$this->renderAdmin();
-							}
-						} else {
-							$this->renderIndex();
-						}
-
-						break;
-					case 'admin':
-						$this->renderAdmin();
-						break;
-					case 'bio':
-						$this->renderForm(JVB_USER[$this->role]['profile']);
-						break;
-					case 'settings':
-						$this->renderSettings();
-						break;
-					case 'integrations':
-					case 'bluesky':
-					case 'cloudflare':
-					case 'facebook':
-					case 'google-maps':
-					case 'google-my-business':
-					case 'helcim':
-					case 'instagram':
-					case 'square':
-					case 'umami':
-						$this->renderIntegrations($page);
-						break;
-					case 'approval':
-						$this->renderApprovals();
-						break;
-					default:
-						$this->renderCRUD($page);
-						break;
-				}
-
-				echo jvbLoadingScreen();
-				$this->renderFooter();
-
-				// Get buffer contents and clean buffer
-				return ob_get_clean();
-			}
+			$constantSlug
 		);
 
-        // Return empty string to prevent default template
-        return '';
-    }
+		$this->renderFooter();
+		return ob_get_clean();
+//		$integrationSlugs = array_map(function($name) {
+//			return sanitize_title(str_replace('_', '-', $name));
+//		}, array_keys(JVB()->getAvailableServices(false)));
+//
+//		if ($page === 'integrations' || in_array($page, $integrationSlugs)) {
+//			// Check integration access
+//			if ($page === 'integrations') {
+//				if (!Site::hasAnyIntegration('user', $this->role)) {
+//					$this->redirectToDashboard();
+//				}
+//			} else {
+//				if (!Site::hasIntegration($page, 'user', $this->role)) {
+//					$this->redirectToDashboard();
+//				}
+//			}
+//		} elseif ($page === 'bio') {
+//			// Bio page logic
+//			$permission = JVB_USER[$this->role]['profile'] ?? false;
+//			if (!$permission || (!current_user_can('manage_'.$permission) && !current_user_can('manage_options'))) {
+//				$this->redirectToDashboard();
+//			}
+//		} elseif ($page === 'settings') {
+//			// Settings page logic
+//			if (!current_user_can('manage_settings') && !current_user_can('manage_options')) {
+//				$this->redirectToDashboard();
+//			}
+//		} elseif ($page === 'approval') {
+//			// Approval page logic
+//			if (!current_user_can('skip_moderation')) {
+//				$this->redirectToDashboard();
+//			}
+//		} elseif ($page !== 'dash') {
+//			// Regular content type - check permission
+//			$type = match($page) {
+//				'menu-item' => 'menu_item',
+//				'events'    => 'event',
+//				default => $page
+//			};
+//
+//			$permission = $this->getPermissionForType($type);
+//			if (!current_user_can($permission)) {
+//				$this->redirectToDashboard();
+//			}
+//		}
+//		// Enqueue needed styles/scripts
+//
+//		$this->cache->delete($page);
+//		echo $this->cache->remember(
+//			$page,
+//			function() use ($page) {
+//				ob_start();
+//				$this->renderHeader();
+//
+//				switch ($page) {
+//					case 'dash':
+//						if (current_user_can('manage_options')) {
+//							$content = apply_filters('jvbAdminDashboard', '');
+//
+//							if ($content !== '') {
+//								echo $content;
+//							}else {
+//								$this->renderAdmin();
+//							}
+//						} else {
+//							$this->renderIndex();
+//						}
+//
+//						break;
+//					case 'admin':
+//						$this->renderAdmin();
+//						break;
+//					case 'bio':
+//						$this->renderForm(JVB_USER[$this->role]['profile']);
+//						break;
+//					case 'settings':
+//						$this->renderSettings();
+//						break;
+//					case 'integrations':
+//					case 'bluesky':
+//					case 'cloudflare':
+//					case 'facebook':
+//					case 'google-maps':
+//					case 'google-my-business':
+//					case 'helcim':
+//					case 'instagram':
+//					case 'square':
+//					case 'umami':
+//						$this->renderIntegrations($page);
+//						break;
+//					case 'approval':
+//						$this->renderApprovals();
+//						break;
+//					default:
+//						$this->renderCRUD($page);
+//						break;
+//				}
+//
+//				echo jvbLoadingScreen();
+//				$this->renderFooter();
+//
+//				// Get buffer contents and clean buffer
+//				return ob_get_clean();
+//			}
+//		);
+//
+//        // Return empty string to prevent default template
+//        return '';
+	}
+
+	protected function renderPage(string $page):string
+	{
+		return '<h1>Whoops</h1>
+		<p>It seems this page isn\'t configured yet.</p>
+		<p>If this keeps happening, maybe contact the admin.</p>';
+	}
 
     /**
      * Enqueues necessary scripts
@@ -287,52 +518,22 @@
      */
     public function dashboardScripts():void
     {
-        if (is_post_type_archive(BASE.'dash') || is_singular(BASE.'dash')) {
+		if (!is_singular(BASE.'dash') && !is_post_type_archive(BASE.'dash')) {
+            return;
+        }
+		IconsManager::for('forms')->enqueueIconStyles();
+		IconsManager::for('dash')->enqueueIconStyles();
 
+		wp_enqueue_script('jvb-form');
+		wp_enqueue_script('jvb-selector');
+		wp_enqueue_script('jvb-uploader');
+		wp_enqueue_script('jvb-content');
 
-//        wp_enqueue_style('quill-css', 'https://cdn.quilljs.com/1.3.6/quill.snow.css');
-
-
-            wp_enqueue_script('jvb-loading');
-			wp_enqueue_script('jvb-form');
-
-
-            // Consolidate all dashboard settings
-            wp_localize_script('jvb-loading', 'dashboardSettings', array(
-                'loadingMessages' => array(
-                    'default' => 'Loading...',
-                    'error' => 'Failed to load page'
-                ),
-                'strings' => array(
-                    'deleteConfirm' => 'Are you sure you want to delete this item?',
-                    'bulkDeleteConfirm' => 'Are you sure you want to delete these items?',
-                    'deleteSuccess' => 'Item(s) deleted successfully',
-                    'deleteError' => 'Error deleting item(s)',
-                    'saveSuccess' => 'Changes saved successfully',
-                    'saveError' => 'Error saving changes',
-                    'loadError' => 'Error loading content'
-                ),
-                'currentUser' => array(
-                    'id' => $this->user->ID,
-                    'name' => $this->user->display_name,
-                    'role' => array_values($this->user->roles)[0] ?? '',
-                    'type' => str_replace(BASE, '', array_values($this->user->roles)[0]),
-                    'city' => '', // Add if needed,
-                    'artistID'  => $this->userLink,
-                )
-            ));
-
-            wp_enqueue_script('jvb-selector');
-            wp_enqueue_script('jvb-uploader');
-            wp_enqueue_script('jvb-content');
-            wp_enqueue_script('jvb-crud');
-//            wp_enqueue_script('jvb-dashboard-navigator');
-
-            $page = $this->getCurrentPage();
+		$page = $this->getCurrentPageSlug();
 
             switch ($page) {
                 case 'notifications':
-					if (jvbSiteHasNotifications()) {
+					if (Site::has('notifications')) {
 						wp_enqueue_script('jvb-notification-manager');
 					}
                     break;
@@ -342,31 +543,37 @@
 					break;
                 case 'admin':
                 case 'dash':
-                    if (current_user_can('manage_options') && apply_filters('jvbAdminDashboard', '') === '') {
-                        wp_enqueue_script(
-                        'jvb-admin',
-                        JVB_URL . 'assets/js/min/admin.min.js',
-                        [
-                            'jvb-queue',
-                            'jvb-loading'
-                        ],
-                        [
-                            'strategy' => 'defer',
-                            'in_footer' => true
-                        ]
-                    );
+//                    if (current_user_can('manage_options') && apply_filters('jvbAdminDashboard', '') === '') {
+//                        wp_enqueue_script(
+//                        'jvb-admin',
+//                        JVB_URL . 'assets/js/min/admin.min.js',
+//                        [
+//                            'jvb-queue',
+////                            'jvb-loading'
+//                        ],
+//                        [
+//                            'strategy' => 'defer',
+//                            'in_footer' => true
+//                        ]
+//                    );
 
-                    wp_localize_script(
-                        'jvb-admin',
-                        'jvbAdmin',
-                        [
-                            'nonce' => wp_create_nonce('itsme')
-                        ]
-                    );
-                    }
+//                    wp_localize_script(
+//                        'jvb-admin',
+//                        'jvbAdmin',
+//                        [
+//                            'nonce' => wp_create_nonce('itsme')
+//                        ]
+//                    );
+//                    }
+					break;
+				case 'seo':
+					wp_enqueue_script('jvb-schema');
+					break;
+				default:
+					wp_enqueue_script('jvb-crud');
 					break;
             }
-			if (jvbSiteHasFavourites()) {
+			if (Site::has('favourites')) {
 				 wp_enqueue_script('jvb-favourites');
 				wp_localize_script('jvb-favourites-manager', 'favouritesSettings', [
 					'strings' => [
@@ -382,23 +589,61 @@
 
 			wp_enqueue_script('jvb-creator');
 
-			if (jvbSiteHasForum()) {
+			if (Site::has('forum')) {
 			wp_enqueue_script('jvb-news');
 			}
 			do_action('jvbDashScripts', $page);
-        }
     }
 
-    protected function getCurrentPage():string
+   protected function getCurrentPageTitle():string
     {
-		global $wp;
-		$dash = str_replace('dash/', '', $wp->request);
-		if (str_starts_with($dash, 'integrations/')) {
-			$dash = str_replace('integrations/', '', $dash);
+		if (is_post_type_archive(BASE.'dash')) {
+			return 'dash';
 		}
 
-		return ($dash === '') ? 'dash' : $dash;
+        global $post;
+        if (!$post) {
+            return '';
+        }
+        return html_entity_decode($post->post_title);
     }
+   protected function getCurrentPageSlug():string
+    {
+		if (is_post_type_archive(BASE.'dash')) {
+			return 'dash';
+		}
+
+        global $post;
+        if (!$post) {
+            return '';
+        }
+
+        return $post->post_name;
+    }
+	protected function getIcon(string $slug, string $page):string
+	{
+		return $this->cache->remember('icon_'.sanitize_title($page), function() use ($slug, $page) {
+			$icon = sanitize_title($page);
+			if (!is_numeric($slug)) {
+				$registrar =Registrar::getInstance($slug);
+				if ($registrar) {
+				return $registrar->getIcon();
+				}
+
+			}
+			return $icon;
+		});
+	}
+	protected function getSlug(string $slug, string $page):string
+	{
+		return $this->cache->remember('slug_'.sanitize_title($page), function() use ($slug, $page) {
+			if (!is_numeric($slug)) {
+				return $slug;
+			} else {
+				return sanitize_title($page);
+			}
+		});
+	}
 
     protected function renderHeader():void
     {
@@ -406,46 +651,44 @@
         <!DOCTYPE html>
     <html <?php language_attributes(); ?>>
         <head>
-            <title><?= (array_key_exists('dashboard_title', JVB_SITE)) ? JVB_SITE['dashboard_title'] : 'Dashboard | '.get_bloginfo('name') ?></title>
+            <title><?= Site::dashboardTitle()??'Dashboard | '.get_bloginfo('name') ?></title>
             <meta charset="<?php bloginfo('charset'); ?>">
             <meta name="viewport" content="width=device-width, initial-scale=1.0">
             <?php
-			$pages = jvbGetUserDashboardPages();
+			$pages = $this->getUserAllowedPages();
+
 			foreach($pages as $page) {
 				$page = str_replace('_', '-', $page);
 				$link = ($page === 'dash') ? '/'.$page : "/dash/$page";
 				?>
-				<link rel="preconnect" href="<?= get_home_url(2, $link)?>"/>
+				<link rel="preconnect" href="<?= get_home_url(null, $link)?>"/>
 				<?php
 			}
 			 ?>
 			 <link rel="preconnect" href="<?= get_home_url()?>"/>
             <?php wp_head(); ?>
         </head>
-    <body class="dashboard<?= ' '.$this->getCurrentPage()?>">
+    <body class="dashboard<?= ' '.$this->getCurrentPageSlug()?>">
         <?php jvbAccessibility();?>
         <header>
-            <?php
-            $checked = (is_user_logged_in() && current_user_can('prefers_dark_theme', true)) ? ' checked' : '';
-            $title = ($checked == '') ? 'Toggle Dark Mode' : 'Toggle Light Mode';
-            echo '<label title="'.$title.'" id="theme-switch" class="toggle-switch" for="theme-switcher">
-        <input class="theme-switch row" id="theme-switcher" type="checkbox"'.$checked.' role="switch" name="dark-mode"><span class="slider">'.
-                 jvbIcon('light').
-                 jvbIcon('dark').'</span></label>';
-            ?>
-            <p class="title">
-                <a href="<?= get_home_url(); ?>" rel="home" title="Back to Site">
-                    <?= jvbIcon('logo-basic'); ?>
-                </a>
-            </p>
+			<?= jvbDarkModeToggle() ?>
+			<?php
+			$function = BASE.'render_core_site_logo';
+			if (function_exists($function)) {
+				echo $function([],'');
+			} else {
+				echo JVB()->blocks()->render_core_site_logo([],'');
+			}
+			?>
 
-            <nav>
-                <ul>
-                    <?= jvbNotificationMenu() ?>
-                    <?= jvbHelpMenu() ?>
-                </ul>
-            </nav>
-        </header>
+			<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
@@ -455,45 +698,146 @@
     {
         ?>
         </section>
+
+		<?php
+		$menu = new Navigation('sidebar');
+		$menuClasses = ['col', 'a-start', 'nowrap'];
+		$itemClasses = ['col'];
+		$menu->addClass('col a-start')->hasToggle()->defaultMenuClasses($menuClasses);
+		$menu->defaultItemClasses($itemClasses);
+		$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::getFeatured('is_content', 'term')
+		);
+		$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);
+							$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();
+		 ?>
+
         <footer class="col">
-            <nav class="dashboard-nav">
+        	<?= jvbLoadingScreen() ?>
+        	<?= TaxonomySelector::outputSelectorModal() ?>
+<!--            <nav class="dashboard-nav">-->
                 <?php
-                $current_page = $this->getCurrentPage();
-                $pages = jvbGetUserDashboardPages()?:[];
-
-				global $jvb_everything;
-                echo '<ul>';
-                foreach ($pages as $page) {
-                    // Add data-page attribute for the navigator
-                    $active = ($current_page == $page) ? ' class="current"' : '';
-                    $current = ($current_page == $page) ? ' aria-current="page"' : '';
-					$icon = (array_key_exists($page, $jvb_everything)) ? $jvb_everything[$page]['icon'] ?? $page : $page;
-
-					$title = $this->getTitle($page);
-					$page = str_replace('_', '-', $page);
-
-					$link = ($page === 'dash') ? '/'.$page : "/dash/$page";
-
-                    printf(
-                        '<li%s><a href="%s"%s data-page="%s" data-dash title="%s">%s<span>%s</span></a></li>',
-                        $active,
-                        get_home_url(2, $link),
-                        $current,
-                        $page,
-                        $title,
-                        jvbIcon($icon, ['title'=> $title]),
-                        $title
-                    );
-                }
-
-                echo '</ul>';
+//                $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>
+<!--            </nav>-->
         </footer>
 
 
  		<?php
-        do_action('jvbRenderDashboardSettings', $this->getCurrentPage());
+        do_action('jvbRenderDashboardSettings', $this->getCurrentPageSlug());
 		?>
         <?php wp_footer(); ?>
 
@@ -503,37 +847,50 @@
         <?php
     }
 
-    protected function renderIndex():void
+    public function renderIndex(string $content, string $page):string
     {
-        $name = get_post_meta($this->userLink, BASE.'firstname', true);
-        $name = ($name === '') ? $this->user->display_name : $name;
+		if ($page !== '' && $page !== 'dash') {
+			return $content;
+		}
 
-        echo '<h1 style="text-transform:none;margin-top:2em!important;">Hey '.$name.'</h1>';
+		if (Site::has('referrals')) {
+			$whatever = JVB()->referrals()->getReferralWelcomeMessage($this->user->ID);
+			if (!empty($whatever)) {
+				return $whatever;
+			}
+		}
+
+		ob_start();
+        $name = ($this->user->first_name !== '') ? $this->user->first_name : $this->user->display_name;
+
+        echo '<h1>Hey '.$name.'</h1>';
         echo '<p>Welcome back!</p>';
 
-        $pages = jvbGetUserDashboardPages();
-
+        $pages = $this->getUserAllowedPages();
 
         echo '<h2>What would you like to do today?</h2>';
 
-        global $jvb_everything;
-        echo '<ul>';
-        foreach ($pages as $page) {
-
+        echo '<ul class="dashboard">';
+        foreach ($pages as $slug => $page) {
+			if ($page === 'dash') {
+				continue;
+			}
             $title = $this->getTitle($page);
-            $url = sanitize_title($title);
+
             $description = $this->getDescription($page);
 
+			$slug = $this->getSlug($slug, $page);
+			$icon = $this->getIcon($slug, $page);
             if ($title !== '') {
-                echo '<li><p><a href="'.get_home_url(2, '/dash/'.$url.'/').'"
-                    data-page="'.$url.'" data-dash>'.jvbIcon($page).ucfirst($title).'</a></p>'.$description.'</li>';
+                echo '<li><p><a href="'.get_home_url(null, '/dash/'.$slug.'/').'"
+                    data-page="'.$slug.'" data-dash>'.jvbDashIcon($icon).ucwords($title).'</a></p></li>';
             }
 
         }
         echo '</ul>';
 
         echo '<p>Everything saves auto-magically, so rest easy.</p>';
-
+		return ob_get_clean();
     }
     /**
      * Similar to CRUD, except it only manages a single item, such as a user's profile or a shop
@@ -543,10 +900,7 @@
      */
     protected function renderForm(string $type):void
     {
-        if (!current_user_can('manage_'.$type)) {
-            wp_redirect(get_home_url(2, '/dash'));
-            exit;
-        }
+
         wp_enqueue_script(
             'jvb-bio-manager',
             JVB_URL.'assets/js/min/bioManager.min.js',
@@ -562,10 +916,6 @@
 
     protected function renderSettings():void
     {
-        if (!current_user_can('manage_options') && !current_user_can('manage_settings')) {
-            wp_redirect(get_home_url(2, '/dash'));
-            exit;
-        }
 		wp_enqueue_script('jvb-form');
         wp_enqueue_script(
             'jvb-bio-manager',
@@ -581,7 +931,7 @@
 		if ($content !== '') {
 			echo $content;
 		} else {
-		jvbRenderSections($this->user->ID, 'user', jvbUserRole());
+			jvbRenderSections($this->user->ID, 'user', jvbUserRole());
 		}
 
     }
@@ -593,14 +943,14 @@
 		if (!empty($integrations)) {
 			$out = '<nav class="integrations"><ul>';
 
-			$url = get_home_url(2, '/dash/integrations/');
-			$out .= '<li><a href="'.$url.'">'.jvbIcon('plugs-connected').'Integrations</a></li>';
+			$url = get_home_url(null, '/dash/integrations/');
+			$out .= '<li><a href="'.$url.'">'.jvbDashIcon('plugs-connected').'Integrations</a></li>';
 			foreach ($integrations as $name=> $integration) {
 				if (!JVB()->userCanConnect($name, $this->user->ID) || !$integration->hasDefaults()) {
 					continue;
 				}
 				$link = sanitize_title(str_replace('_', '-',$name));
-				$out .= '<li><a href="'.$url.$link.'">'.jvbIcon($integration->icon).$integration->getTitle().'</a></li>';
+				$out .= '<li><a href="'.$url.$link.'">'.jvbDashIcon($integration->icon).$integration->getTitle().'</a></li>';
 			}
 			$out .= '</ul></nav>';
 		}
@@ -609,12 +959,6 @@
 
 	protected function renderIntegrations(string $page):void
 	{
-
-		//TODO: Make manage_integrations permission
-//		if (!current_user_can('manage_integrations')) {
-//			wp_redirect(get_home_url(2, '/dash'));
-//			exit;
-//		}
 		echo $this->getIntegrationsMenu();
 		$map = [
 			'google-my-business' => 'gmb',
@@ -623,7 +967,7 @@
 		$connection = (array_key_exists($page, $map)) ? $map[$page] : $page;
 		if ($connection !== 'integrations') {
 
-			$userID = (jvbSiteHasMembership()) ? $this->user->ID : null;
+			$userID = (Site::has('has_membership')) ? $this->user->ID : null;
 			$integration = JVB()->connect($connection, $userID);
 
 			echo '<h1>Managing '.$integration->title.'</h1>';
@@ -653,24 +997,20 @@
 
     protected function renderApprovals():void
     {
-        if (!current_user_can('skip_moderation')) {
-            wp_redirect(get_home_url(2, '/dash'));
-            exit;
-        }
         ?>
         <div class="approvals container">
             <nav class="tabs row start" role="tablist">
                 <button type="button" class="tab active" data-tab="summary" role="tab" aria-selected="true">
-                    <h2><?= jvbIcon('all')?>All</h2>
+                   <?= jvbDashIcon('infinity')?>All
                 </button>
                 <button type="button" class="tab" data-tab="artists" role="tab" aria-selected="false">
-                    <h2><?= jvbIcon('artists')?>Artists</h2>
+                    <?= jvbDashIcon('users-three')?>Artists
                 </button>
                 <button type="button" class="tab" data-tab="terms" role="tab" aria-selected="false">
-                    <h2><?= jvbIcon('style')?>Terms</h2>
+                   <?= jvbDashIcon('hash')?>Terms
                 </button>
                 <button type="button" class="tab" data-tab="yours" role="tab" aria-selected="false">
-                    <h2><?= jvbIcon('artist')?>Yours</h2>
+                    <?= jvbDashIcon('user')?>Yours
                 </button>
             </nav>
         </div>
@@ -723,50 +1063,30 @@
 
     }
 
-    protected function renderCRUD(string $type):void
+
+    public function renderAdmin(string $content, string $page):string
     {
-		$type = match($type) {
-			'menu-item' => 'menu_item',
-			'events'	=> 'event',
-			default => $type
-		};
-
-		$permission = JVB_CONTENT[$type]['plural']??$type.'s';
-        if (!current_user_can('edit_'.$permission)) {
-            wp_redirect(get_home_url(2, '/dash'));
-            exit;
-        }
-        $crud = new CRUD($type);
-        $crud->render();
-
-    }
-
-    protected function renderAdmin():void
-    {
-        //TODO: This has to be built from the settings from setup.php
-        if (!current_user_can('manage_options')) {
-            wp_redirect(get_home_url(2, '/dash'));
-            exit;
-        }
-
+		if ($page !== '' && $page !== 'dash') {
+			return $content;
+		}
+		ob_start();
         ?>
         <nav class="tabs row start" role="tablist">
         <?php
         $i=1;
-        $content = JVB_CONTENT;
-        $contentTax = array_filter(JVB_TAXONOMY, function ($tax) {
-            return jvbCheck('is_content', $tax);
-        });
-        $taxonomies = JVB_TAXONOMY;
-        foreach($contentTax as $key => $config) {
-            unset($taxonomies[$key]);
+        $content = Registrar::getRegistered('post');
+        $contentTax = Registrar::getFeatured('is_content', 'term');
+        $taxonomies = Registrar::getRegistered('term');
+        foreach($contentTax as $index => $tax) {
+            unset($taxonomies[$index]);
         }
         $content = array_merge($content, $contentTax);
-        foreach ($content as $type => $settings) {
+        foreach ($content as $type) {
+			$registrar = Registrar::getInstance($type);
             $active = ($i === 1) ? ' active' : '';
             ?>
             <button type="button" class="tab<?=$active?>" data-tab="<?=$type?>" role="tab" aria-selected="<?= ($active !== '') ? 'true' : 'false'?>">
-                <h2><?=jvbIcon($type)?> <?= $settings['plural'] ?></h2>
+                <h2><?=jvbDashIcon($registrar->getIcon())?> <?= $registrar->getPlural() ?></h2>
             </button>
             <?php
             $i++;
@@ -777,8 +1097,9 @@
             <option> ... Taxonomy</option>
             <?php
 
-            foreach ($taxonomies as $type => $settings) {
-                echo '<option value="'.$type.'">'.$settings['plural'].'</option>';
+            foreach ($taxonomies as $type) {
+				$taxRegistrar = Registrar::getInstance($type);
+                echo '<option value="'.$type.'">'.$taxRegistrar->getPlural().'</option>';
             }
             ?>
         </select>
@@ -793,19 +1114,19 @@
             'vertical',
             'TAB NAV:',
             '',
-            jvbIcon('down'),
-            jvbIcon('right'))?>
+            jvbDashIcon('caret-double-down'),
+            jvbDashIcon('caret-double-right'))?>
 
     </div>
     <div class="items-container">
     </div>
 
     <?php
-    global $jvb_everything;
 
-    foreach ($jvb_everything as $type => $settings) {
-        $meta = new MetaManager(null, 'form');
-        $fields = jvbGetFields($type);
+
+
+    foreach (Registrar::getRegistered() as $type) {
+        $fields = Registrar::getFieldsFor($type);
         ?>
         <template class="<?= $type ?>Table">
             <table>
@@ -852,18 +1173,18 @@
         <template class="<?= $type ?>Row">
             <tr>
                 <td>
-                     <?= jvbIcon('grab') ?>
+                     <?= jvbDashIcon('dots-six-vertical') ?>
                  </td>
                  <td data-id="actions" class="col">
                      <?= jvbRenderToggleTextField(
                          'public',
                          '',
                          '',
-                         jvbIcon('show'),
-                         jvbIcon('hide'))
+                         jvbDashIcon('eye'),
+                         jvbDashIcon('eye-closed'))
                      ?>
                      <button type="button" data-action="edit">
-                         <?= jvbIcon('edit') ?>
+                         <?= jvbDashIcon('pencil-simple') ?>
                     </button>
                 </td>
                 <?php
@@ -874,7 +1195,7 @@
                             <?php
                             $config['type'] = 'text';
                             $config['description'] = '';
-                            $meta->render('form', $n, $config);
+                            Form::render($n, null, $config);
                             ?>
                         </td>
                         <?php
@@ -889,8 +1210,310 @@
         echo jvbNewModal(
             'edit-modal '.$type,
             'Edit '.ucfirst($type),
-            $meta->renderForm('admin', [], $fields)
+            jvbRenderForm('admin', $fields)
         );
         }
+		return ob_get_clean();
     }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+	/**
+	 * Get all possible dashboard pages regardless of user
+	 * Used during dashboard build process
+	 * @return array
+	 */
+	protected function getAllDashboardPages():array
+	{
+		$cacheKey = 'all_pages';
+		$pages = $this->cache->get($cacheKey);
+		if ($pages === false || JVB_TESTING) {
+			$pages = [];
+			$pages[] = 'SEO';
+			// Add feature-dependent pages (non-config)
+			if (Site::has('referrals')) {
+				$pages[] = 'Referrals';
+			}
+			$membership = Site::membership();
+			if ($membership && $membership->has('can_invite')) {
+				$pages[] = 'Invites';
+			}
+
+			if ($membership && $membership->has('term_approval')) {
+				$pages[] = 'Approvals';
+			}
+
+			if ($membership && $membership->has('forum')) {
+				$pages[] = 'News';
+			}
+
+			if ($membership && $membership->has('member_content')) {
+				$pages[] = 'Metrics';
+			}
+
+			if (Site::has('favourites')) {
+				$pages[] = 'Favourites';
+			}
+
+			if (!empty(Registrar::getFeatured('karma'))) {
+				$pages[] = 'Karmic Score';
+			}
+
+			if (Site::has('notifications')) {
+				$pages[] = 'Notifications';
+			}
+
+			if (Site::has('support')) {
+				$pages[] = 'Support';
+			}
+
+			if (Site::hasAnyIntegration()) {
+				$pages[] = 'Integrations';
+			}
+
+			// Add all content types (with config keys)
+			foreach (Registrar::getRegistered('post') as $slug) {
+				$registrar = Registrar::getInstance($slug);
+				$pages[$slug] = $registrar->getPlural();
+			}
+
+			foreach (Registrar::getRegistered('term') as $slug) {
+				$registrar = Registrar::getInstance($slug);
+				$pages[$slug] = $registrar->getPlural();
+			}
+
+			// Allow filtering
+			$pages = apply_filters('jvbAllDashboardPages', $pages);
+
+			// Remove duplicates while preserving keys
+			$pages = array_unique($pages);
+
+			// Dash home always first
+			array_unshift($pages, 'dash');
+
+			$this->cache->set($cacheKey, $pages, WEEK_IN_SECONDS);
+		}
+
+		return $pages;
+	}
+
+
+	/**
+	 * Get pages that a specific user is allowed to access
+	 * Filters based on capabilities and features
+	 * @param int|null $userID Optional user ID (defaults to current user)
+	 * @return array
+	 */
+	public function getUserAllowedPages(?int $userID = null):array
+	{
+		if ($userID === null) {
+			$user = $this->user;
+			$userID = $user->ID;
+		} else {
+			$user = get_userdata($userID);
+		}
+
+		if (!$user || !$this->userHasDashboardAccess($user)) {
+			return [];
+		}
+
+
+		$pages = $this->cache->get($userID);
+
+		if ($pages === false || JVB_TESTING) {
+			if (user_can($userID, 'manage_options')) {
+				// Admin gets all pages as flat array
+				$pages = $this->getAllDashboardPages();
+				// Extract just the values (slugs)
+				$this->cache->set($userID, $pages, WEEK_IN_SECONDS);
+				return $pages;
+			}
+			$roles = array_map('jvbNoBase', $user->roles);
+			$pages = $this->getAllDashboardPages();
+
+			$canSkip = user_can($userID, 'skip_moderation');
+			foreach($pages as $key => $slug) {
+				//Default to Remove pages
+				$remove = true;
+				if (!is_numeric($key)) {
+					$registrar = Registrar::getInstance($key);
+					$membership = Site::membership();
+					$type  = $registrar? $registrar->getType() : false;
+					if ($type) {
+						$permission = JVB()->roles()->getPermission('edit', $key);
+					}
+					switch ($type) {
+						case 'content':
+							if (user_can($userID, $permission)) {
+								$remove = false;
+							}
+							break;
+						case 'taxonomy':
+							$registrar = Registrar::getInstance($key);
+							if ($registrar && $registrar->hasFeature('is_content') && (!empty(JVB()->roles()->getOwnedTerms($userID, $key)) || !empty(JVB()->roles()->getManagedTerms($userID, $key)))){
+								$remove = false;
+							} else if (count(array_intersect($registrar->registrar->for, array_keys($pages))) > 0) {
+								$remove = false;
+							}
+							break;
+					}
+				} else {
+					switch ($slug) {
+						case 'Integrations':
+							foreach($roles as $role) {
+								if (Registrar::getInstance($role)->hasAnyIntegrations()) {
+									$remove = false;
+								}
+							}
+							break;
+						case 'invites':
+							$canInvite = JVB_MEMBERSHIP['can_invite']??[];
+							foreach ($roles as $role) {
+								if (array_key_exists($role, $canInvite)) {
+									$remove = false;
+								}
+							}
+							if ($remove) {
+								//TODO: Figure out what $config was supposed to be
+								if ($canSkip || ($registrar && $registrar->hasFeature('invitable'))) {
+									$remove = false;
+								}
+							}
+							break;
+						case 'Approvals':
+							$canApprove = false;
+							if ($membership && $membership->has('term_approval')) {
+								if (array_key_exists('can_approve', JVB_MEMBERSHIP)) {
+									foreach ($roles as $role) {
+										if (in_array($role, JVB_MEMBERSHIP['can_approve'])) {
+											$canApprove = true;
+										}
+									}
+								} else {
+									//Anyone can approve
+									$canApprove = true;
+								}
+							}
+							if ($canSkip && $canApprove) {
+								$remove = false;
+							}
+							break;
+						case 'news':
+							$canAccess = false;
+							if (array_key_exists('member_only', JVB_MEMBERSHIP)){
+								foreach ($roles as $role) {
+									if (in_array($role, JVB_MEMBERSHIP['member_only'])) {
+										$canAccess = true;
+									}
+								}
+							}
+							if ($canAccess && $canSkip) {
+								$remove = false;
+							}
+							break;
+						case 'metrics':
+							foreach ($roles as $role) {
+								if (!empty(Registrar::getInstance($role)?->getCreatable())) {
+									$remove = false;
+								}
+							}
+							break;
+						case 'karmic-score':
+							foreach ($roles as $role) {
+								$contents = Registrar::getInstance($role)?->getCreatable();
+								if (!empty($contents)) {
+									$hasKarma = Registrar::getFeatured('karma');
+									$remove = empty(array_intersect($contents, $hasKarma));
+								}
+							}
+							break;
+						case 'dash':
+						case 'Referrals':
+						case 'favourites':
+						case 'notifications':
+						case 'support':
+							$remove = false;
+							break;
+						default:
+							break;
+					}
+				}
+				if ($remove) {
+					unset($pages[$key]);
+				}
+			}
+
+			//Allow Filtering
+			$pages = apply_filters('jvbUserDashboardPages', $pages, $user->roles, $userID);
+			$pages = array_unique($pages);
+
+			$this->cache->set($userID, $pages, WEEK_IN_SECONDS);
+		}
+
+		return $pages;
+	}
+
+	/**
+	 * Check if user can create content
+	 * Replaces jvbUserCanCreate()
+	 * @param int $userID
+	 * @return bool
+	 */
+	protected function userCanCreate(int $userID = 0):bool
+	{
+		$user = ($userID === 0) ? wp_get_current_user() : get_userdata($userID);
+		$roles = array_intersect(
+			$this->getRolesWithDashboard(),
+			array_map('jvbNoBase', $user->roles)
+		);
+
+		$creatable = [];
+		foreach ($roles as $role) {
+			$roleCreatable = Registrar::getInstance($role)?->getCreatable();
+			$creatable = array_merge($creatable, $roleCreatable);
+		}
+
+		return !empty($creatable);
+	}
+
+	/**
+	 * Get user roles that have dashboard access
+	 * Replaces jvbRolesWithDashboard()
+	 * @return array
+	 */
+	protected function getRolesWithDashboard():array
+	{
+		return Registrar::getFeatured('has_dashboard', 'user');
+	}
+
+	/**
+	 * Check if user has dashboard access
+	 * @param WP_User $user
+	 * @return bool
+	 */
+	protected function userHasDashboardAccess(WP_User $user):bool
+	{
+		if (user_can($user, 'manage_options')) {
+			return true;
+		}
+
+		$userRoles = array_map('jvbNoBase', $user->roles);
+		$dashboardRoles = $this->getRolesWithDashboard();
+
+		return count(array_intersect($dashboardRoles, $userRoles)) > 0;
+	}
+
 }

--
Gitblit v1.10.0