slug; $this->cache = Cache::for('dashboard')->connect('post'); if (JVB_TESTING) { $this->cache->flush(); } $this->registerDashboard(); $this->buildPages(); $this->buildSections(); $this->setCurrentUserPages(); //Since this is loaded via JVBase, it's already running on init $this->registerHooks(); $doit = apply_filters('jvb_base_dashboard_dash', true); if ($doit) { add_action(BASE.'dashboard_page_dash', [$this, 'renderMain'], 10); } add_action(BASE.'dashboard_page_account', [$this, 'renderAccount'], 10); add_action(BASE.'dashboard_page_reset_password', [$this, 'resetPasswordPage'], 10); } protected function registerDashboard():void { $dash = Registrar::forPost($this->slug, 'Dashboard', 'Dashboards'); $dash->setIcon('gauge') ->make([ 'rewrite' => [ 'slug' => $this->slug, 'with_front' => false, ], 'supports' => [ 'title', 'editor', 'custom-fields'], 'hierarchical' => true, 'has_archive' => true, ])->setAll([ 'system' ]); $dash->register(); } 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(self::$based) && !is_post_type_archive(self::$based) && !is_404()) { return; } if (!is_404()) { if (!is_user_logged_in()) { $this->redirectToLogin(); } elseif (!isOurPeople() && !current_user_can('manage_options')) { $this->redirectToHome(); } else { $page = $this->getCurrentPageSlug(); if (array_key_exists($page, ['', 'dash'])) { return; } $page = $this->getCurrentPage(); if (!$page) { $this->redirectToDashboard(); } if (!in_array($page, $this->pages)) { error_log('Looking for page: '.$page->getSlug(). ' in: '.print_r($this->pages, true)); error_log('[DashboardManager]::handleRedirect could not find page for '.$page->getTitle()); return; } $permission = $page->getPermission(); if (!empty($permission) && !$this->handlePermission($page)) { error_log('[DashboardManager]::handleRedirect User cannot manage '.$page->getTitle()); $this->redirectToDashboard(); } } return; } global $wp; if (is_404() && str_starts_with($wp->request, $this->slug.'/') || $wp->request === $this->slug) { error_log('404 on dashboard URL, redirecting to dashboard home'); $this->redirectToDashboard(); } } public function dashboardTemplates(string $template):string { if (!is_singular(self::$based) && !is_post_type_archive(self::$based)) { return $template; } $page = $this->getCurrentPage(); if ($page && !empty($page->getIcon())) { add_filter('jvbLoadingIcon', [$page, 'getIcon']); } ob_start(); jvbInlineStyles('nav'); jvbInlineStyles('dash'); jvbInlineStyles('forms'); $this->renderHeader(); echo $page->render(); $this->renderFooter(); $got = ob_get_clean(); echo $got; return $got; } protected function renderHeader():void { $page = $this->getCurrentPage(); ?> > <?= $page->getTitle() . ' | Dashboard' ?> pages as $page) { if (empty($page->getPermission()) || current_user_can($page->getPermission())) { echo ''; } } ?>
'core/site-logo', 'attrs' => [], ]); } ?>
outputSidebarNavigation(); ?> getCurrentPageSlug()); ?> 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()); $all = $this->getSectionPagesAndSections(); unset($all['dash']); // uasort($pages, fn($a, $b) => $a->getOrder() <=> $b->getOrder()); uasort($all, fn($a, $b) => $a->getOrder() <=> $b->getOrder()); foreach ($all as $slug => $item) { if (is_a($item, Section::class)) { $this->buildMenuSection($item, $menu); } else { $menuItem = $menu->addItem($item->getTitle(), jvbDashIcon($item->getIcon())); $menuItem->url($item->getURL()); if ($this->getCurrentPage() === $item) { $menuItem->current(); } } } return $menu->render(); } protected function buildMenuSection(Section $section, Navigation $menu, array $pages = []):void { $all = $this->getSectionPagesAndSections($section->getSlug()); if (empty($all)) { return; } uasort($all, fn($a, $b) => $a->getOrder() <=> $b->getOrder()); $page = $menu->addItem($section->getTitle(), jvbDashIcon($section->getIcon())); $main = $this->getMainPage($section->getSlug()); if ($main && $section->getIsLink()) { $page->url($main->getURL()); if ($this->getCurrentPage() === $page) { $page->current(); } } $submenu = $page->submenu(); foreach ($all as $slug=>$item) { if (is_a($item, Section::class)) { $this->buildMenuSection($item, $submenu); } elseif ($slug !== $section->getSlug()) { $menuItem = $submenu->addItem($item->getTitle(), jvbDashIcon($item->getIcon())); $menuItem->url($item->getURL()); if ($this->getCurrentPage() === $item) { $menuItem->current(); } } } } protected function getSectionPagesAndSections(?string $section = null):array { $pages = $this->getSectionPages($section); $pages = array_filter($pages, function ($p) { return empty($p->getPermission()) || current_user_can('manage_options') || current_user_can($p->getPermission()); }); $sections = $this->getSectionSections($section); $sections = array_filter($sections, function ($s) { $check = empty($s->getPermission()) || current_user_can('manage_options') || current_user_can($s->getPermission()); $pages = $this->getSectionPagesAndSections($s->getSlug()); return $check && !empty($pages); }); return array_merge($pages, $sections); } 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 { if (!is_singular(self::$based) && !is_post_type_archive(self::$based)) { 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'); $page = $this->getCurrentPage(); foreach ($page->getScripts() as $script) { wp_enqueue_script($script); } } public function excludeDashboard(array $IDs):array { $exclude = $this->cache->remember( 'dashboardIDs', function() { return get_posts([ 'post_type' => self::$based, 'posts_per_page' => -1, 'fields' => 'ids', ]); }); if (!empty($exclude)) { $IDs = array_merge($IDs, $exclude); } return $IDs; } private function buildPages():void { $this->addPage('Dashboard', 'dash','door'); $this->buildContentPages(10); $this->buildReferrals(30); $this->buildFavourites(5); $this->buildKarma(15); $this->buildNotifications(20); $this->buildMembership(); $this->buildIntegrations(35); $this->buildSettingsPages(40); $this->buildAccountPages(50); } private function buildContentPages(int $order = 10):void { $content = Registrar::getRegistered('post'); $this->sections['content'] = new Section('Your Content', 'content', 'book-bookmark'); $this->sections['content']->setOrder($order); 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'); $page->setScripts(['jvb-crud', 'jvb-creator']); $this->sections[$c] = new Section($registrar->getPlural(), $c, $registrar->getIcon(), 'content'); $this->sections[$c]->setPermission(RoleManager::getPermissionName('edit', $c)); $this->sections[$c]->setIsLink(true); $taxonomies = $registrar->registrar->taxonomies; foreach ($taxonomies as $tax) { $taxReg = Registrar::getInstance($tax); if ($taxReg) { $taxPage = $this->addPage($taxReg->getPlural(), $tax, $registrar->getIcon(), $page->getID()); $taxPage->setPermission(RoleManager::getPermissionName('edit', $c)); $taxPage->setSection($c); $taxPage->setScripts(['jvb-crud', 'jvb-creator']); } } $this->pages[$c] = $page; } $contentTax = Registrar::withFeature('is_content', 'term'); foreach ($contentTax as $c) { $registrar = Registrar::getInstance($c); $page = $this->addPage($registrar->getPlural(), $c, $registrar->getIcon()); $page->setPermission(RoleManager::getPermissionName('edit', $c)); $page->setSection($c); //TODO: add artist management and invitations, if applicable } } private function buildSettingsPages(int $order = 40):void { $this->sections['settings'] = new Section('Settings', 'settings', 'gear-six'); $this->sections['settings']->setOrder($order); $seo = $this->addPage('SEO', 'seo', 'robot'); $seo->setSection('settings'); $seo->setPermission('manage_options'); $seo->setScripts(['jvb-schema']); //TODO: Create permission that user has editable content $this->pages['seo'] = $seo; } private function buildAccountPages(int $order = 50):void { if (Site::has('support')) { $this->sections['support'] = new Section('Support', 'support', 'question'); $this->sections['support']->setOrder($order + 5); $page = $this->addPage('Support', 'support', 'question'); $page->setSection('support'); } $account = $this->addPage('Account', 'account', 'user-circle'); $account->setScripts(['jvb-form','jvb-tabs']); $account->setSection('account'); $this->sections['account'] = new Section('Account', 'account', 'user-circle'); $this->sections['account']->setOrder($order); $this->sections['account']->setIsLink(true); $password = $this->addPage('Reset Password', 'reset-password', 'password', $account->getID()); $password->setOrder(2); $password->setScripts(['jvb-form', 'jvb-tabs']); $password->setSection('account'); } private function buildReferrals(int $order = 30):void { if (Site::has('referrals')) { $page = $this->addPage('Referrals', 'referrals', 'hand-heart'); $page->setOrder($order); } } private function buildMembership():void { $membership = Site::membership(); if ($membership) { $buildNotification = false; if ($membership->has('can_invite')) { $buildNotification = true; $page = $this->addPage('Invite', 'invite', ''); $page->setSection('notifications'); } if ($membership->has('term_approval')) { $buildNotification = true; $page = $this->addPage('Approvals', 'approvals', 'check-circle'); $page->setSection('notifications'); } if ($membership->has('forum')) { $page = $this->addPage('Forum', 'forum', 'chats-teardrop'); $page->setScripts(['jvb-news']); } if ($membership->has('member_content')) { $page = $this->addPage('Metrics', 'metrics', 'chart-line'); } if ($buildNotification){ $this->buildNotificationSection(); } } } private function buildFavourites(int $order = 5):void { if (Site::has('favourites')) { $this->sections['favourites'] = new Section('Favourites', 'favourites', 'heart'); $this->sections['favourites']->setOrder($order); $page = $this->addPage('Favourites', 'favourites', 'heart'); $page->setSection('favourites'); $page->setScripts(['jvb-favourites']); $lists = $this->addPage('Lists', 'lists', 'list-heart', $page->getID()); $lists->setSection('favourites'); $lists->setScripts(['jvb-favourites']); $permissions = $this->addPage('Permissions', 'permissions', 'hand-heart', $page->getID()); $permissions->setSection('favourites'); $permissions->setScripts(['jvb-favourites']); } } private function buildKarma(int $order = 15):void { if (!empty(Registrar::withFeature('karma'))) { $page = $this->addPage('Karmic', 'karmic', 'arrow-fat-up'); $page->setOrder($order); } } private function buildNotifications(int $order = 20):void { if (Site::has('notifications')) { $this->buildNotificationSection($order); $page = $this->addPage('Notifications', 'notifications', 'bell'); $page->setSection('notifications'); $page->setScripts(['jvb-notifications-manager']); $page = $this->addPage('Permissions', 'permissions', 'gear-six'); $page->setSection('notifications'); } } private function buildNotificationSection(int $order = 20):void { $this->sections['notifications'] = new Section('Notifications', 'notifications', 'bell'); $this->sections['notifications']->setOrder($order); } private function buildIntegrations(int $order = 35):void { if (Site::hasAnyIntegration()) { $page = $this->addPage('Integrations', 'integrations', 'plugs-connected'); $page->setSection('settings'); $page->setOrder($order); $page->setPermission('user_has_integrations'); $page->setScripts(['jvb-integrations']); $parent = $page->getID(); $this->sections['integrations'] = new Section('Integrations', 'integrations', 'plugs-connected', 'settings'); $this->sections['integrations']->setOrder($order); $this->sections['integrations']->setIsLink(true); 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(); if ($integration::hasExtraOptions()) { $page = $this->addPage($title, $title, $icon, $parent); $page->setScripts(['jvb-integrations']); $page->setSection('integrations'); } } } } public function addPage(string $title, string $slug = '', string $icon = '', int $parent = 0):DashboardPage { $page = new DashboardPage($title, $slug, $icon, $parent); $this->pages[$slug] = $page; return $page; } protected function buildSections():void { $sections = array_values(array_filter(array_unique(array_map(function ($page) { return $page->getSection(); }, $this->pages)))); $missing = array_diff($sections, array_map(function($s) { return $s->getSlug(); }, $this->sections)); if (!empty($missing)) { error_log('[DashboardManager]::buildSections Section mentioned, but not defined: '.print_r($missing, true)); } // foreach ($sections as $section) { // $isLink = $this->hasMainPage($section); // if ($isLink) { // $this->sections[$section]->setIsLink(true); // } // } } protected function getSectionPages(?string $section = null):array { return array_filter($this->pages, function($page) use ($section) { return $page->getSection() === $section; }); } protected function getSectionSections(?string $section = null):array { return array_filter($this->sections, function($s) use ($section) { return $s->getParent() === $section; }); } protected function getMainPage(string $section):DashboardPage|false { $section = DashboardPage::sanitizeString($section); $mainPage = array_values(array_filter($this->pages, function ($page) use ($section) { return $page->getSlug() === $section; })); return empty($mainPage) ? false : $mainPage[0]; } protected function hasMainPage(string $section):bool { $section = DashboardPage::sanitizeString($section); 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; } protected function setCurrentUserPages():void { $this->currentUserPages = array_filter($this->pages, function($page) { return current_user_can('manage_options') || empty($page->getPermission()) || $this->handlePermission($page); }); } protected function handlePermission($page):bool { return match($page->getPermission()) { 'user_has_integrations' => $this->handleIntegrationPermission(), default => current_user_can($page->getPermission()) }; } protected function handleIntegrationPermission():bool { $user = wp_get_current_user(); $role = jvbUserRole($user->ID); if (current_user_can('manage_options')) { return true; } $registrar = Registrar::getInstance($role); if (!$registrar) { return false; } return $registrar->hasAnyIntegrations(); } #[NoReturn]protected function redirectToLogin():void { wp_redirect(wp_login_url(get_home_url(null, '/'.$this->slug))); exit; } #[NoReturn]protected function redirectToDashboard():void { wp_redirect(get_home_url(null, '/'.$this->slug)); exit; } #[NoReturn]protected function redirectToHome():void { wp_redirect(get_home_url()); exit; } protected function getCurrentPage():DashboardPage|false { $slug = $this->getCurrentPageSlug(); $page = array_filter($this->pages, function($p) use ($slug) { return $p->getSlug() === $slug; }); if (empty($page)) { error_log('[DashboardManager]::getCurrentPage Could not get configuration for '.$slug); return false; } return $page[array_key_first($page)]; } protected function getCurrentPageSlug():string { if (is_post_type_archive(self::$based)) { return 'dash'; } global $post; if (!$post) { return ''; } return $post->post_name; } public function renderMain(string $slug):void { if ($slug !== 'dash') { return; } $user = get_userdata(get_current_user_id()); $name = ($user->first_name !== '') ? $user->first_name : $user->display_name; echo sprintf( '

Hey %s

Welcome back!

', $name ); $reg = Registrar::getInstance(jvbUserRole($user->ID)); if ($reg && !empty($reg->getCreatable())) { echo '

Everything saves auto-magically - so rest easy.

'; } } public function renderAccount():void { $user = get_userdata(get_current_user_id()); $role = jvbUserRole($user->ID); $registrar = Registrar::getInstance($role); if ($registrar) { $meta = Meta::forUser($user->ID); $fields = $registrar->getFields(); $sections = $registrar->getSections(); $tabs = new Tabs(); foreach ($sections as $slug => $config) { $tab = $tabs->addTab($slug); $tab->title($config['title']); if (!empty($config['description'])) { $tab->description($config['description']); } if (!empty($config['icon'])) { $tab->icon($config['icon']); } $content = implode('', array_map(function ($f) use ($meta) { return Form::renderFrom($meta,$f); },$config['fields'])); $tab->content($content); } echo '
'.jvbFormStatus().$tabs->render().'
'; } else { $fields = Fields::getUserFields(); $meta = new Meta($user->ID, 'user', $fields); echo Form::renderFormFrom($meta, 'user', [ 'heading' => 'Your Account', 'description' => [ 'You can set your information here.', 'To reset your password, check the side menu under "Account"' ], 'submit' => true ]); } $script = 'document.addEventListener(\'DOMContentLoaded\', async function () { window.auth.subscribe(event => { if (event === \'auth-loaded\') { const form = document.querySelector(\'form[data-save="user"]\'); if (!form || !window.jvbForm) return; window.jvbForm.registerForm(form); } }); });'; // wp_add_inline_script('jvb-form', $script); } public function resetPasswordPage():void { ?>

Reset Your Password

If you'd like to reset your password, you can do so here.

Alternatively, you can always login using the magic link, which sets a temporary password for 15 minutes.

'; } ?> renderLoginForm('resetpass'); ?>