cache = CacheManager::for('dashboard', WEEK_IN_SECONDS); $this->cache->invalidate(); add_action('init', [$this, 'registerDashboard']); if (!$this->isRegistered()) { add_action('init', [$this, 'buildDashboard']); } $this->user = wp_get_current_user(); $this->role = jvbUserRole(); $this->userLink = (int)get_user_meta($this->user->ID, BASE.'link', true); 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); } /** * Registers the custom post type that handles the dashboard * @return void */ public function registerDashboard():void { $plural = 'Dashboards'; $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" ], 'menu_icon' => jvbCSSIcon('gauge'), 'public' => true, 'publicly_queryable' => true, 'show_in_menu' => true, 'show_in_admin_bar' => false, 'has_archive' => true, 'hierarchical' => true, 'rewrite' => array( 'slug' => 'dash', 'with_front' => false ), 'capability_type' => 'post', 'supports' => array('title', 'editor', 'custom-fields') )); } /** * Redirect all non-admin users from wp-admin to custom dashboard */ public function redirectFromAdmin() { // Allow admins to access wp-admin if needed if (current_user_can('manage_options')) { return; } // Redirect to custom dashboard $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):array { $pages = $this->getAllDashboardPages(); $key = array_search($page, $pages); if ($key === false || is_numeric($key)) { return []; } return Features::getConfig($key); } /** * 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 { $config = $this->getConfig($slug); if (!empty($config)) { return $config['dash_title']??$config['plural']; } 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->getCurrentPage(); // Dashboard home is always accessible (if authenticated) if ($page === '' || $page === 'dash') { return; } // 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(); return; } } } /** * Ensures the necessary pages are created * @return void */ public function buildDashboard():void { $manageableContent = $this->getAllDashboardPages(); foreach ($manageableContent as $key => $slug) { if ($slug === 'dash') { continue; } $existing = get_page_by_path($slug, OBJECT, BASE.'dash'); if ($existing) { continue; } $title = $this->getTitle($slug); $ID = wp_insert_post(array( 'post_title' => $title, 'post_name' => $slug, 'post_type' => BASE.'dash', 'post_status' => 'publish', )); if ($title === 'Integrations') { $this->buildIntegrationPages($ID); } } update_option(BASE.'dashboard_registered', true); remove_action('init', [$this, 'buildDashboard']); } /** * 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(); $slug = sanitize_title($title); // Check if integration page already exists $existing = get_posts([ 'post_type' => BASE.'dash', 'name' => $slug, 'post_parent' => $parentID, 'posts_per_page' => 1, ]); if (!empty($existing)) { continue; // Skip if exists } wp_insert_post([ 'post_title' => $title, 'post_name' => $slug, 'post_type' => BASE.'dash', 'post_status' => 'publish', 'post_parent' => $parentID ]); } } protected function getDescription(string $page):string { $config = $this->getConfig($page); if (!empty($config)) { $description = (array_key_exists('dash_description', $config)) ? $config['dash_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!'; break; case 'metrics': $description = 'See what kind of traffic you\'re getting. (coming soon)'; break; case 'favourites': $description = 'See what you favourited, and create, manage, and share lists.'; break; case 'support': $description = 'Sometimes we all need a hand. This is your direct access to the site admin - or text Jake at 825-823-9916'; break; case 'settings': $description = 'Control your privacy and email frequency.'; break; } } 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 * @param string $template * * @return string */ public function dashboardTemplates(string $template):string { if (!is_singular(BASE.'dash') && !is_post_type_archive(BASE.'dash')) { return $template; } // Get current page/section $page = $this->getCurrentPage(); $integrationSlugs = array_map(function($name) { return sanitize_title(str_replace('_', '-', $name)); }, array_keys(JVB()->getAvailableServices(false))); // 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->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->renderHeader(); // Pass to page handler $constantSlug = $this->getConstantSlug($page); echo apply_filters( 'jvbDashboardPage', $this->renderPage($page), $page, $constantSlug ); $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 (!Features::hasAnyIntegration('user', $this->role)) { // $this->redirectToDashboard(); // } // } else { // if (!Features::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 '

Whoops

It seems this page isn\'t configured yet.

If this keeps happening, maybe contact the admin.

'; } /** * Enqueues necessary scripts * @return void */ public function dashboardScripts():void { if (!is_singular(BASE.'dash') && !is_post_type_archive(BASE.'dash')) { return; } 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'); $page = $this->getCurrentPage(); switch ($page) { case 'notifications': if (Features::forSite()->has('notifications')) { wp_enqueue_script('jvb-notification-manager'); } break; case 'integrations': wp_enqueue_script('jvb-integrations'); 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 ] ); wp_localize_script( 'jvb-admin', 'jvbAdmin', [ 'nonce' => wp_create_nonce('itsme') ] ); } break; } if (Features::forSite()->has('favourites')) { wp_enqueue_script('jvb-favourites'); wp_localize_script('jvb-favourites-manager', 'favouritesSettings', [ 'strings' => [ 'loadError' => 'Error loading favourites', 'saveError' => 'Error updating favourite', 'removeSuccess' => 'Removed from favourites', 'addSuccess' => 'Added to favourites' ] ]); } wp_enqueue_script('jvb-creator'); if (Features::forSite()->has('forum')) { wp_enqueue_script('jvb-news'); } do_action('jvbDashScripts', $page); } protected function getCurrentPage():string { if (is_post_type_archive(BASE.'dash')) { return 'dash'; } global $post; if (!$post) { return ''; } return $post->post_name; } protected function renderHeader():void { ?> > <?= (array_key_exists('dashboard_title', JVB_SITE)) ? JVB_SITE['dashboard_title'] : 'Dashboard | '.get_bloginfo('name') ?> getUserAllowedPages(); foreach($pages as $page) { $page = str_replace('_', '-', $page); $link = ($page === 'dash') ? '/'.$page : "/dash/$page"; ?>
'. jvbIcon('light', ['title'=> 'Light Mode']). jvbIcon('dark', ['title'=>'Dark Mode']). ''; ?>

0) { $url = wp_get_attachment_image_url( $icon); if ($url) { $out = ''; } } if ($out == '') { $out =jvbIcon('home'); } ?>

getCurrentPage()); ?> user->first_name !== '') ? $this->user->first_name : $this->user->display_name; echo '

Hey '.$name.'

'; echo '

Welcome back!

'; $pages = $this->getUserAllowedPages(); echo '

What would you like to do today?

'; echo ''; echo '

Everything saves auto-magically, so rest easy.

'; return ob_get_clean(); } /** * Similar to CRUD, except it only manages a single item, such as a user's profile or a shop * @param string $type * * @return void */ protected function renderForm(string $type):void { wp_enqueue_script( 'jvb-bio-manager', JVB_URL.'assets/js/min/bioManager.min.js', array('jvb-queue', 'sortablejs', 'quill-js', 'jvb-selector'), '1.0.0', true ); wp_localize_script('jvb-bio-manager', 'bioSettings', [ 'type' => 'bio_update', ]); jvbRenderSections($this->userLink, 'post', $type); } protected function renderSettings():void { wp_enqueue_script('jvb-form'); wp_enqueue_script( 'jvb-bio-manager', JVB_URL.'assets/js/min/bioManager.min.js', array('jvb-client-queue', 'sortablejs', 'quill-js', 'jvb-taxonomy-selector'), '1.0.0', true ); wp_localize_script('jvb-bio-manager', 'bioSettings', [ 'type' => 'user_settings', ]); $content = apply_filters('jvbDashboardSettings', ''); if ($content !== '') { echo $content; } else { jvbRenderSections($this->user->ID, 'user', jvbUserRole()); } } protected function getIntegrationsMenu():string { $integrations = JVB()->getAvailableServices(false); $out = ''; if (!empty($integrations)) { $out = ''; } return $out; } protected function renderIntegrations(string $page):void { echo $this->getIntegrationsMenu(); $map = [ 'google-my-business' => 'gmb', 'google-maps' => 'maps' ]; $connection = (array_key_exists($page, $map)) ? $map[$page] : $page; if ($connection !== 'integrations') { $userID = (jvbSiteHasMembership()) ? $this->user->ID : null; $integration = JVB()->connect($connection, $userID); echo '

Managing '.$integration->title.'

'; $integration->renderDefaults(); } else { ?>
getAvailableServices(); foreach ($all as $name) { if (current_user_can('manage_options')) { $userID = null; } else { $userID = $this->user->ID; } JVB()->connect($name, $userID)->renderConnection(); } ?>
Name Requester Actions
Name Requester Actions

ArtistsManage artists here

$settings) { $meta = new MetaManager(null, 'form'); $fields = jvbGetFields($type); ?> renderForm('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 = []; // Add feature-dependent pages (non-config) if (Features::forSite()->has('referrals')) { $pages[] = 'referrals'; } if (Features::forMembership()->has('can_invite')) { $pages[] = 'invites'; } if (Features::forMembership()->has('term_approval')) { $pages[] = 'approvals'; } if (Features::forMembership()->has('forum')) { $pages[] = 'news'; } if (Features::forMembership()->has('member_content')) { $pages[] = 'metrics'; } if (Features::forSite()->has('favourites')) { $pages[] = 'favourites'; } if (Features::anyContentHas('karma') || Features::anyTaxonomyHas('karma') || Features::anyUserHas('karma')) { $pages[] = 'karmic-score'; } if (Features::forSite()->has('notifications')) { $pages[] = 'notifications'; } if (Features::forSite()->has('support')) { $pages[] = 'support'; } if (Features::hasAnyIntegration()) { $pages[] = 'integrations'; } // Add all content types (with config keys) foreach (JVB_CONTENT as $slug => $config) { $pages[$slug] = sanitize_title($config['plural']); } foreach (JVB_TAXONOMY as $slug=>$config) { $pages[$slug] = sanitize_title($config['plural']); } // 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 available to a specific role * @param string $role The role slug (with or without BASE prefix) * @return array */ protected function getRolePages(string $role):array { $role = jvbNoBase($role); if (!array_key_exists($role, JVB_USER)) { return []; } return Features::forUser($role)->getDashboardPages(); } /** * 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 []; } $cacheKey = "user_pages_{$userID}"; $pages = $this->cache->get($cacheKey); 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($cacheKey, $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)) { $type = Features::getType($key); if ($type) { $permission = RoleManager::getPlural($key); } switch ($type) { case 'content': if (!user_can($userID, "edit_{$permission}")) { $remove = false; } break; case 'taxonomy': $config = Features::getConfig($key, 'taxonomy'); if (array_key_exists('is_content', $config) && $config['is_content'] && (user_can($userID, "own_{$key}") || user_can($userID, "manage_{$key}"))) { $remove = false; } break; } } else { switch ($slug) { case 'integrations': foreach($roles as $role) { if (Features::hasAnyIntegration('user', $role)) { $remove = false; } } break; case 'invites': $canInvite = JVB_MEMBERSHIP['can_invite']??[]; foreach ($roles as $role) { if (array_key_exists($role, $canInvite)) { $remove = false; } } if ($remove) { if ($canSkip || array_key_exists('invitable', $config)) { $remove = false; } } break; case 'approvals': $canApprove = false; if (Features::forMembership()->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(Features::forUser($role)->getCreatableContent())) { $remove = false; } } break; case 'karmic-score': foreach ($roles as $role) { $contents = Features::forUser($role)->getCreatableContent(); if (!empty($contents)) { foreach($contents as $content) { if (Features::forContent($content)->has('karma')) { $remove = false; } } } } break; 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($cacheKey, $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 = Features::forUser($role)->getCreatableContent(); $creatable = array_merge($creatable, $roleCreatable); } return !empty($creatable); } /** * Get user roles that have dashboard access * Replaces jvbRolesWithDashboard() * @return array */ protected function getRolesWithDashboard():array { return array_keys(array_filter(JVB_USER, function ($role) { return Features::forUser(array_search($role, JVB_USER))->has('has_dashboard'); })); } /** * 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; } /** * Get the capability needed to access a content type * @param string $type * @return string */ protected function getPermissionForType(string $type):string { // Check if it's a registered content type if (array_key_exists($type, JVB_CONTENT)) { $plural = JVB_CONTENT[$type]['plural']; return 'edit_'.$plural; } // Default to edit_{type}s return 'edit_'.$type.'s'; } /** * Invalidate dashboard page cache for a user or all users * Call this when user roles or permissions change * @param int|null $userID Specific user to invalidate, null for all * @return void */ public function invalidatePagesCache(?int $userID = null):void { if ($userID !== null) { $this->cache->delete("user_pages_{$userID}"); } else { // Invalidate all user caches by invalidating the group $this->cache->invalidate(); } } }