Jake Vanderwerf
2026-04-26 86c6cd3cc099d2480932ede03c12cea01e625c94
inc/managers/DashboardManager.php
@@ -1,9 +1,10 @@
<?php
namespace JVBase\managers;
use JVBase\forms\TaxonomySelector;use JVBase\managers\CRUD;
use JVBase\meta\MetaManager;
use JVBase\utility\Features;
use JVBase\forms\TaxonomySelector;
use JVBase\base\Site;
use JVBase\meta\Form;
use JVBase\registrar\Registrar;
use JVBase\ui\Navigation;
use WP_User;
@@ -17,18 +18,19 @@
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 = CacheManager::for('dashboard', WEEK_IN_SECONDS);
        $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->user->ID);
        $this->userLink = (int)get_user_meta($this->user->ID, BASE.'link', true);
@@ -67,18 +69,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,
@@ -100,12 +105,24 @@
     */
    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
         $this->redirectToDashboard();
         if (is_user_logged_in() && isOurPeople()) {
         $this->redirectToDashboard();
      }
    }
   protected function redirectToLogin():void
@@ -121,14 +138,14 @@
   }
   protected function getConfig(string $page):array
   protected function getConfig(string $page):Registrar|false
   {
      $pages = $this->getAllDashboardPages();
      $key = array_search($page, $pages);
      if ($key === false || is_numeric($key)) {
         return [];
         return false;
      }
      return Features::getConfig($key);
      return Registrar::getInstance($key)??false;
   }
   /**
@@ -148,9 +165,9 @@
   protected function getTitle(string $slug):string
   {
      $config = $this->getConfig($slug);
      if (!empty($config)) {
         return $config['dash_title']??$config['plural'];
      $registrar = $this->getConfig($slug);
      if ($registrar) {
         return $registrar->getConfig('dashboard')['title']??$registrar->getPlural();
      }
      return ucwords(str_replace('-', ' ', str_replace('_', ' ', $slug)));
   }
@@ -195,10 +212,10 @@
         $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();
            return;
         }
      }
   }
@@ -274,9 +291,9 @@
    protected function getDescription(string $page):string
    {
      $config = $this->getConfig($page);
        if (!empty($config)) {
            $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) {
@@ -324,10 +341,10 @@
        // Get current page/section
        $page = $this->getCurrentPageTitle();
      $config = $this->getConfig($page);
      if(!empty($config)) {
         add_filter('jvbLoadingIcon', function() use ($config) {
            return $config['icon'];
      $registrar = $this->getConfig($page);
      if($registrar) {
         add_filter('jvbLoadingIcon', function() use ($registrar) {
            return $registrar->getIcon();
         });
      }
      $integrationSlugs = array_map(function($name) {
@@ -339,14 +356,14 @@
         // Pass along to the Integrations template handler which knows to check for subpages
         $page = 'integrations';
      }
      echo $this->renderDashboard($page);
      //TODO: Reenable
//    echo $this->cache->remember(
//       $page,
//       function() use ($page) {
//          return $this->renderDashboard($page);
//       }
//    );
//    echo $this->renderDashboard($page);
      echo $this->cache->remember(
         $page,
         function() use ($page) {
            return $this->renderDashboard($page);
         }
      );
      return '';
    }
@@ -365,6 +382,7 @@
      $this->renderHeader();
      // Pass to page handler
      $constantSlug = $this->getConstantSlug($page);
        echo apply_filters(
         'jvbDashboardPage',
         $this->renderPage($page),
@@ -381,11 +399,11 @@
//    if ($page === 'integrations' || in_array($page, $integrationSlugs)) {
//       // Check integration access
//       if ($page === 'integrations') {
//          if (!Features::hasAnyIntegration('user', $this->role)) {
//          if (!Site::hasAnyIntegration('user', $this->role)) {
//             $this->redirectToDashboard();
//          }
//       } else {
//          if (!Features::hasIntegration($page, 'user', $this->role)) {
//          if (!Site::hasIntegration($page, 'user', $this->role)) {
//             $this->redirectToDashboard();
//          }
//       }
@@ -499,8 +517,8 @@
      if (!is_singular(BASE.'dash') && !is_post_type_archive(BASE.'dash')) {
            return;
        }
      wp_enqueue_style('jvb-icons-dash');
      wp_enqueue_style('jvb-icons-forms');
      IconsManager::for('forms')->enqueueIconStyles();
      IconsManager::for('dash')->enqueueIconStyles();
      wp_enqueue_script('jvb-form');
      wp_enqueue_script('jvb-selector');
@@ -511,7 +529,7 @@
            switch ($page) {
                case 'notifications':
               if (Features::forSite()->has('notifications')) {
               if (Site::has('notifications')) {
                  wp_enqueue_script('jvb-notification-manager');
               }
                    break;
@@ -521,28 +539,28 @@
               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');
@@ -551,7 +569,7 @@
               wp_enqueue_script('jvb-crud');
               break;
            }
         if (Features::forSite()->has('favourites')) {
         if (Site::has('favourites')) {
             wp_enqueue_script('jvb-favourites');
            wp_localize_script('jvb-favourites-manager', 'favouritesSettings', [
               'strings' => [
@@ -567,7 +585,7 @@
         wp_enqueue_script('jvb-creator');
         if (Features::forSite()->has('forum')) {
         if (Site::has('forum')) {
         wp_enqueue_script('jvb-news');
         }
         do_action('jvbDashScripts', $page);
@@ -583,8 +601,7 @@
        if (!$post) {
            return '';
        }
        return $post->post_title;
        return html_entity_decode($post->post_title);
    }
   protected function getCurrentPageSlug():string
    {
@@ -604,10 +621,11 @@
      return $this->cache->remember('icon_'.sanitize_title($page), function() use ($slug, $page) {
         $icon = sanitize_title($page);
         if (!is_numeric($slug)) {
            $config = Features::getConfig($slug);
            if (array_key_exists('icon', $config)) {
               $icon = $config['icon'];
            $registrar =Registrar::getInstance($slug);
            if ($registrar) {
            return $registrar->getIcon();
            }
         }
         return $icon;
      });
@@ -680,6 +698,7 @@
                <ul>
                    <?= jvbNotificationMenu() ?>
                    <?= jvbHelpMenu() ?>
                    <li><a href="<?=wp_logout_url(get_home_url())?>" title="Logout"><?=jvbIcon('sign-out')?></a></li>
                </ul>
            </nav>
        </header>
@@ -723,9 +742,12 @@
      //Content
         //content types
            //Taxonomies
      $availableContent = array_filter($pages, function($page, $key) {
         return !is_numeric($key) && array_key_exists($key, JVB_CONTENT);
      $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'))
@@ -733,25 +755,30 @@
            ->defaultMenuClasses($menuClasses)
            ->defaultItemClasses($itemClasses);
         foreach ($availableContent as $slug => $page) {
            $config = JVB_CONTENT[$slug];
            $item = $content->addItem($page, jvbDashIcon($config['icon']))
            $registrar = Registrar::getInstance($slug);
            $item = $content->addItem($page, $registrar->getIcon())
               ->url($this->baseURL.'/'.$slug);
            $taxonomies = array_filter(JVB_TAXONOMY, function ($value, $key) use ($slug) {
               return in_array($slug, $value['for_content']);
            },1);
            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 => $config) {
                  $itemMenu->addItem($config['plural'], $config['icon'])
                     ->url($this->baseURL.'/'.$s);
            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')
@@ -839,7 +866,7 @@
         return $content;
      }
      if (Features::forSite()->has('referrals')) {
      if (Site::has('referrals')) {
         $whatever = JVB()->referrals()->getReferralWelcomeMessage($this->user->ID);
         if (!empty($whatever)) {
            return $whatever;
@@ -856,7 +883,7 @@
        echo '<h2>What would you like to do today?</h2>';
        echo '<ul>';
        echo '<ul class="dashboard">';
        foreach ($pages as $slug => $page) {
         if ($page === 'dash') {
            continue;
@@ -953,7 +980,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>';
@@ -1060,20 +1087,19 @@
        <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><?=jvbDashIcon($settings['icon']??$key)?> <?= $settings['plural'] ?></h2>
                <h2><?=jvbDashIcon($registrar->getIcon())?> <?= $registrar->getPlural() ?></h2>
            </button>
            <?php
            $i++;
@@ -1084,8 +1110,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>
@@ -1109,11 +1136,10 @@
    <?php
    $jvb_everything = array_merge(JVB_CONTENT, JVB_TAXONOMY);
    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>
@@ -1182,7 +1208,7 @@
                            <?php
                            $config['type'] = 'text';
                            $config['description'] = '';
                            $meta->render('form', $n, $config);
                            Form::render($n, null, $config);
                            ?>
                        </td>
                        <?php
@@ -1197,10 +1223,9 @@
        echo jvbNewModal(
            'edit-modal '.$type,
            'Edit '.ucfirst($type),
            $meta->renderForm('admin', [], $fields)
            jvbRenderForm('admin', $fields)
        );
        }
      return ob_get_clean();
    }
@@ -1232,53 +1257,55 @@
         $pages = [];
         $pages[] = 'SEO';
         // Add feature-dependent pages (non-config)
         if (Features::forSite()->has('referrals')) {
         if (Site::has('referrals')) {
            $pages[] = 'Referrals';
         }
         if (Features::forMembership()->has('can_invite')) {
         $membership = Site::membership();
         if ($membership && $membership->has('can_invite')) {
            $pages[] = 'Invites';
         }
         if (Features::forMembership()->has('term_approval')) {
         if ($membership && $membership->has('term_approval')) {
            $pages[] = 'Approvals';
         }
         if (Features::forMembership()->has('forum')) {
         if ($membership && $membership->has('forum')) {
            $pages[] = 'News';
         }
         if (Features::forMembership()->has('member_content')) {
         if ($membership && $membership->has('member_content')) {
            $pages[] = 'Metrics';
         }
         if (Features::forSite()->has('favourites')) {
         if (Site::has('favourites')) {
            $pages[] = 'Favourites';
         }
         if (Features::anyContentHas('karma') || Features::anyTaxonomyHas('karma') || Features::anyUserHas('karma')) {
         if (!empty(Registrar::getFeatured('karma'))) {
            $pages[] = 'Karmic Score';
         }
         if (Features::forSite()->has('notifications')) {
         if (Site::has('notifications')) {
            $pages[] = 'Notifications';
         }
         if (Features::forSite()->has('support')) {
         if (Site::has('support')) {
            $pages[] = 'Support';
         }
         if (Features::hasAnyIntegration()) {
         if (Site::hasAnyIntegration()) {
            $pages[] = 'Integrations';
         }
         // Add all content types (with config keys)
         foreach (JVB_CONTENT as $slug => $config) {
            $pages[$slug] = $config['plural'];
         foreach (Registrar::getRegistered('post') as $slug) {
            $registrar = Registrar::getInstance($slug);
            $pages[$slug] = $registrar->getPlural();
         }
         foreach (JVB_TAXONOMY as $slug=>$config) {
            $pages[$slug] = $config['plural'];
         foreach (Registrar::getRegistered('term') as $slug) {
            $registrar = Registrar::getInstance($slug);
            $pages[$slug] = $registrar->getPlural();
         }
         // Allow filtering
@@ -1296,21 +1323,6 @@
      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
@@ -1332,15 +1344,14 @@
      }
      $cacheKey = "user_pages_{$userID}";
      $pages = $this->cache->get($cacheKey);
      $pages = false;
      $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($cacheKey, $pages, WEEK_IN_SECONDS);
            $this->cache->set($userID, $pages, WEEK_IN_SECONDS);
            return $pages;
         }
         $roles = array_map('jvbNoBase', $user->roles);
@@ -1351,21 +1362,22 @@
            //Default to Remove pages
            $remove = true;
            if (!is_numeric($key)) {
               $type  = Features::getType($key);
               $registrar = Registrar::getInstance($key);
               $type  = $registrar? $registrar->getType() : false;
               if ($type) {
                  $permission = RoleManager::getPlural($key);
                  $permission = JVB()->roles()->getPermission('edit', $key);
               }
               switch ($type) {
                  case 'content':
                     if (user_can($userID, "edit_{$permission}")) {
                     if (user_can($userID, $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}"))) {
                     $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($config['for_content'], array_keys($pages))) > 0) {
                     } else if (count(array_intersect($registrar->registrar->for, array_keys($pages))) > 0) {
                        $remove = false;
                     }
                     break;
@@ -1374,7 +1386,7 @@
               switch ($slug) {
                  case 'Integrations':
                     foreach($roles as $role) {
                        if (Features::hasAnyIntegration('user', $role)) {
                        if (Registrar::getInstance($role)->hasAnyIntegrations()) {
                           $remove = false;
                        }
                     }
@@ -1387,14 +1399,15 @@
                        }
                     }
                     if ($remove) {
                        if ($canSkip || array_key_exists('invitable', $config)) {
                        //TODO: Figure out what $config was supposed to be
                        if ($canSkip || ($registrar && $registrar->hasFeature('invitable'))) {
                           $remove = false;
                        }
                     }
                     break;
                  case 'Approvals':
                     $canApprove = false;
                     if (Features::forMembership()->has('term_approval')) {
                     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'])) {
@@ -1425,20 +1438,17 @@
                     break;
                  case 'metrics':
                     foreach ($roles as $role) {
                        if (!empty(Features::forUser($role)->getCreatableContent())) {
                        if (!empty(Registrar::getInstance($role)?->getCreatable())) {
                           $remove = false;
                        }
                     }
                     break;
                  case 'karmic-score':
                     foreach ($roles as $role) {
                        $contents = Features::forUser($role)->getCreatableContent();
                        $contents = Registrar::getInstance($role)?->getCreatable();
                        if (!empty($contents)) {
                           foreach($contents as $content) {
                              if (Features::forContent($content)->has('karma')) {
                                 $remove = false;
                              }
                           }
                           $hasKarma = Registrar::getFeatured('karma');
                           $remove = empty(array_intersect($contents, $hasKarma));
                        }
                     }
                     break;
@@ -1462,7 +1472,7 @@
         $pages = apply_filters('jvbUserDashboardPages', $pages, $user->roles, $userID);
         $pages = array_unique($pages);
         $this->cache->set($cacheKey, $pages, WEEK_IN_SECONDS);
         $this->cache->set($userID, $pages, WEEK_IN_SECONDS);
      }
      return $pages;
@@ -1484,7 +1494,7 @@
      $creatable = [];
      foreach ($roles as $role) {
         $roleCreatable = Features::forUser($role)->getCreatableContent();
         $roleCreatable = Registrar::getInstance($role)?->getCreatable();
         $creatable = array_merge($creatable, $roleCreatable);
      }
@@ -1498,9 +1508,7 @@
    */
   protected function getRolesWithDashboard():array
   {
      return array_keys(array_filter(JVB_USER, function ($role) {
         return Features::forUser(array_search($role, JVB_USER))->has('has_dashboard');
      }));
      return Registrar::getFeatured('has_dashboard', 'user');
   }
   /**
@@ -1520,36 +1528,4 @@
      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();
      }
   }
}