<?php
|
namespace JVBase\managers\Dashboard;
|
|
use JetBrains\PhpStorm\NoReturn;
|
use JVBase\forms\TaxonomySelector;
|
use JVBase\base\Site;
|
use JVBase\managers\Cache;
|
use JVBase\managers\IconsManager;use JVBase\managers\RoleManager;use JVBase\meta\Form;use JVBase\meta\Meta;use JVBase\registrar\Fields;use JVBase\registrar\Registrar;
|
use JVBase\ui\Navigation;
|
|
if (!defined('ABSPATH')) {
|
exit;
|
}
|
class DashboardManager {
|
public static string $based;
|
protected string $slug = 'dash';
|
protected string $baseURl;
|
protected array $currentUserPages = [];
|
protected array $pages = [];
|
protected array $sections = [];
|
protected Cache $cache;
|
public function __construct() {
|
self::$based = BASE.$this->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);
|
}
|
|
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 (!in_array($page, $this->pages)) {
|
error_log('Looking for page: '.$page. ' in: '.print_r($this->pages, true));
|
error_log('[DashboardManager]::handleRedirect could not find page for '.$page);
|
return;
|
}
|
$permission = $page->getPermission();
|
if (!empty($permission) && !current_user_can($permission)) {
|
error_log('[DashboardManager]::handleRedirect User cannot manage '.$page);
|
$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();
|
?>
|
<!DOCTYPE html>
|
<html <?php language_attributes(); ?>>
|
<head>
|
<title><?= $page->getTitle() . ' | Dashboard' ?></title>
|
<meta charset="<?php bloginfo('charset'); ?>">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<?php
|
foreach ($this->pages as $page) {
|
if (empty($page->getPermission()) || current_user_can($page->getPermission())) {
|
echo '<link rel="preconnect" href="'.$page->getURL().'">';
|
}
|
}
|
?>
|
<link rel="preconnect" href="<?= get_home_url()?>"/>
|
<?php wp_head(); ?>
|
</head>
|
<body class="dashboard<?= ' '.$this->getCurrentPageSlug()?>">
|
<?php jvbAccessibility();?>
|
<header>
|
<?= jvbDarkModeToggle() ?>
|
<?php
|
$function = BASE.'render_core_site_logo';
|
if (function_exists($function)) {
|
echo $function([],'');
|
} else {
|
echo render_block( [
|
'blockName' => 'core/site-logo',
|
'attrs' => [],
|
]);
|
}
|
?>
|
|
<nav>
|
<ul>
|
<?= jvbNotificationMenu() ?>
|
<?= jvbHelpMenu() ?>
|
<li><a href="<?=wp_logout_url(get_home_url())?>" title="Logout"><?=jvbIcon('sign-out')?></a></li>
|
</ul>
|
</nav>
|
</header>
|
|
<main><section class="replace">
|
<?php
|
}
|
|
protected function renderFooter():void
|
{
|
?>
|
</section>
|
|
<?= $this->outputSidebarNavigation(); ?>
|
|
<footer class="col">
|
<?= jvbLoadingScreen() ?>
|
<?= TaxonomySelector::outputSelectorModal() ?>
|
</footer>
|
|
|
<?php
|
do_action('jvbRenderDashboardSettings', $this->getCurrentPageSlug());
|
?>
|
<?php wp_footer(); ?>
|
|
</body>
|
</html>
|
|
<?php
|
}
|
|
protected function outputSidebarNavigation():string
|
{
|
$menu = new Navigation('sidebar');
|
|
$menuClasses = ['left'];
|
$itemClasses = ['col'];
|
$menu->addClass('sidebar left')->hasToggle()->defaultMenuClasses($menuClasses);
|
$menu->defaultItemClasses($itemClasses);
|
|
//Add the Main Dashboard first
|
$dashboard = $menu->addItem('Dashboard', jvbDashIcon($this->pages['dash']->getIcon()))
|
->url($this->pages['dash']->getURL());
|
|
$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());
|
}
|
}
|
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());
|
}
|
$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());
|
}
|
}
|
|
}
|
|
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->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->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->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()) || current_user_can($page->getPermission());
|
});
|
}
|
|
#[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(
|
'<h1>Hey %s</h1><p>Welcome back!</p>',
|
$name
|
);
|
|
$reg = Registrar::getInstance(jvbUserRole($user->ID));
|
if ($reg && !empty($reg->getCreatable())) {
|
echo '<p>Everything saves auto-magically - so rest easy.</p>';
|
}
|
}
|
|
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();
|
} 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"'
|
]
|
]);
|
}
|
}
|