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 ' ';
}
}
?>
= jvbDarkModeToggle() ?>
'core/site-logo',
'attrs' => [],
]);
}
?>
= $this->outputSidebarNavigation(); ?>
= jvbLoadingScreen() ?>
= TaxonomySelector::outputSelectorModal() ?>
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\services\GoogleMaps',
'square' => 'JVBase\integrations\services\Square',
'facebook' => 'JVBase\integrations\services\Facebook',
'helcim' => 'JVBase\integrations\services\Helcim',
'instagram' => 'JVBase\integrations\services\Instagram',
'bluesky' => 'JVBase\integrations\services\BlueSky',
'gmb' => 'JVBase\integrations\services\GoogleMyBusiness',
'cloudflare' => 'JVBase\integrations\services\Cloudflare',
'umami' => 'JVBase\integrations\services\Umami',
'postmark' => 'JVBase\integrations\services\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 '';
} 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.';
}
?>
= LoginManager::getInstance()->renderLoginForm('resetpass'); ?>