Jake Vanderwerf
2026-05-15 894ec8a6f2ac62edbac7b3b6a88e3666f335c673
inc/managers/DashboardManager.php
@@ -1,9 +1,12 @@
<?php
namespace JVBase\managers;
use JVBase\managers\CRUD;
use JVBase\meta\MetaManager;
use WP_User;
use JVBase\forms\TaxonomySelector;
use JVBase\base\Site;
use JVBase\meta\Form;
use JVBase\registrar\Registrar;
use JVBase\ui\Navigation;
use WP_Error;use WP_Query;use WP_User;
if (!defined('ABSPATH')) {
    exit; // Exit if accessed directly
@@ -15,27 +18,45 @@
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 = new CacheManager('dashboard');
      $this->cache->invalidateGroup('dashboard');
        $this->cache = Cache::for('dashboard', WEEK_IN_SECONDS)->connect('user');
        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);
        $this->user = wp_get_current_user();
        $this->role = jvbUserRole($this->user->ID);
        $this->userLink = (int)get_user_meta($this->user->ID, BASE.'profile_link', true);
      $this->baseURL = get_home_url(null, '/dash');
      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);
      jvb_register_do_once('buildDashboard', [$this, 'activate']);
      add_filter('the_seo_framework_sitemap_exclude_ids', [$this, 'excludeDashboard'], 10, 1);
    }
   public function excludeDashboard(array $ids):array {
      $cached = $this->cache->remember(
         'dashboardIDs',
         function() {
            return get_posts([
               'post_type' => BASE.'dash',
               'posts_per_page' => -1,
               'fields' => 'ids',
            ]);
         });
      return array_merge($ids, $cached);
   }
    /**
     * Registers the custom post type that handles the dashboard
     * @return void
@@ -47,18 +68,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,
@@ -80,89 +104,210 @@
     */
    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
        wp_redirect(home_url('dash'));
        exit;
         if (is_user_logged_in() && isOurPeople()) {
         $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):Registrar|false
   {
      $pages = $this->getAllDashboardPages();
      $key = array_search($page, $pages);
      if ($key === false || is_numeric($key)) {
         return false;
      }
      return Registrar::getInstance($key)??false;
   }
   /**
    * 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
   {
      $registrar = $this->getConfig($slug);
      if ($registrar) {
         return $registrar->getConfig('dashboard')['title']??$registrar->getPlural();
      }
      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->getCurrentPageSlug();
         // Dashboard home is always accessible (if authenticated)
         if ($page === '' || $page === 'dash') {
            return;
         }
         $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();
         }
      }
   }
    /**
     * Ensures the necessary pages ar created
     * Ensures the necessary pages are created
     * @return void
     */
    public function buildDashboard():void
    public function activate():void
    {
        $manageableContent = jvbGetAllDashboardPages();
        foreach ($manageableContent as $slug) {
            $title = $this->getTitle($slug);
         $slug = sanitize_title($title);
        $manageableContent = $this->getAllDashboardPages();
      error_log('[DashboardManager]::buildDashboard Manageable Content: '.print_r($manageableContent, true));
        foreach ($manageableContent as $slug => $page) {
         if ($page === 'dash') {
            continue;
         }
            $ID = wp_insert_post(array(
                'post_title'    => $title,
         $ID = $this->createDashboardPage($slug, $page);
         $registrar = Registrar::getInstance($slug);
         if ($registrar) {
            $create = [
               'new_'   => 'Create New ',
               'edit_'  => 'Edit '
            ];
            $parentID = (int)$ID;
            foreach ($create as $s => $t) {
               $s .= $slug;
               $t .= $page;
               $this->createDashboardPage($s, $t, $parentID);
            }
         }
         if ($page === 'Integrations') {
            $this->buildIntegrationPages($ID);
         }
        }
    }
      public function createDashboardPage(string $slug, string $page, int $parentID = 0):int|WP_Error
      {
         if (is_numeric($slug)) {
            $slug = $this->getSlug($slug, $page);
         }
         $existing = new WP_Query([
            'post_type' => BASE.'dash',
            'name'   => $slug,
            'fields' => 'ids',
            'posts_per_page'  => 1,
         ]);
         if ($existing->have_posts()) {
            return $existing->posts[0];
         }
         $args = [
            'post_title'    => $page,
                'post_name'     => $slug,
                'post_type'     => BASE.'dash',
                'post_status'   => 'publish',
            ));
         if ($title === 'Integrations') {
            $integrations = ['BlueSky', 'Cloudflare', 'Facebook', 'Google Maps', 'Google My Business', 'Helcim', 'Instagram', 'Square', 'Umami'];
            foreach ($integrations as $integration) {
               $slug = sanitize_title($integration);
               wp_insert_post([
                  'post_title'    => $integration,
                  'post_name'    => $slug,
                  'post_type'    => BASE.'dash',
                  'post_status'  => 'publish',
                  'post_parent'  => $ID
               ]);
            }
         ];
         if ($parentID > 0) {
            $args['post_parent'] = $parentID;
         }
        }
        update_option(BASE.'dashboard_registered', true);
        remove_action('init', [$this, 'buildDashboard']);
    }
            return wp_insert_post($args);
      }
    protected function getTitle(string $page):string
    {
        $content = JVB_CONTENT;
        $contentTax = array_filter(JVB_TAXONOMY, function ($tax) {
            return jvbCheck('is_content', $tax);
        });
        $content = array_merge($content, $contentTax);
        $title = '';
   /**
    * 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();
        if (array_key_exists($page, $content)) {
            $config = $content[$page];
            $title = (array_key_exists('dash_title', $config)) ? $config['dash_title'] : $config['plural'];
        } else {
            switch ($page) {
                case 'admin':
                    $title = 'Admin';
                    break;
                default:
               $title = ucwords(str_replace('_', ' ', str_replace('-', ' ', $page)));
            }
        }
         $slug = sanitize_title($title);
         $this->createDashboardPage($slug, $title, $parentID);
      }
   }
        return $title;
    }
    protected function getDescription(string $page):string
    {
        $content = JVB_CONTENT;
        $contentTax = array_filter(JVB_TAXONOMY, function ($tax) {
            return jvbCheck('is_content', $tax);
        });
        $content = array_merge($content, $contentTax);
        if (array_key_exists($page, $content)) {
            $config = $content[$page];
            $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) {
                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!';
@@ -185,14 +330,6 @@
        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
@@ -205,81 +342,175 @@
        if (!is_singular(BASE.'dash') && !is_post_type_archive(BASE.'dash')) {
            return $template;
        }
        if (!isOurPeople() && !current_user_can('manage_options')) {
            wp_redirect(wp_login_url(get_home_url(2, '/dash')));
            exit;
        }
        // Get current page/section
        $page = $this->getCurrentPageTitle();
      $registrar = $this->getConfig($page);
      if($registrar) {
         add_filter('jvbLoadingIcon', function() use ($registrar) {
            return $registrar->getIcon();
         });
      }
      $integrationSlugs = array_map(function($name) {
         return sanitize_title(str_replace('_', '-', $name));
      }, array_keys(JVB()->getAvailableServices(false)));
        $page = $this->getCurrentPage();
      // 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->renderDashboard($page);
      // Enqueue needed styles/scripts
      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->cache->delete($page);
      echo $this->cache->remember(
      $this->renderHeader();
      // Pass to page handler
      $constantSlug = $this->getConstantSlug($page);
        echo apply_filters(
         'jvbDashboardPage',
         $this->renderPage($page),
         $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();
         }
         $constantSlug
      );
        // Return empty string to prevent default template
        return '';
    }
      $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 (!Site::hasAnyIntegration('user', $this->role)) {
//             $this->redirectToDashboard();
//          }
//       } else {
//          if (!Site::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 '<h1>Whoops</h1>
      <p>It seems this page isn\'t configured yet.</p>
      <p>If this keeps happening, maybe contact the admin.</p>';
   }
    /**
     * Enqueues necessary scripts
@@ -287,52 +518,22 @@
     */
    public function dashboardScripts():void
    {
        if (is_post_type_archive(BASE.'dash') || is_singular(BASE.'dash')) {
      if (!is_singular(BASE.'dash') && !is_post_type_archive(BASE.'dash')) {
            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');
//        wp_enqueue_style('quill-css', 'https://cdn.quilljs.com/1.3.6/quill.snow.css');
            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');
//            wp_enqueue_script('jvb-dashboard-navigator');
            $page = $this->getCurrentPage();
      $page = $this->getCurrentPageSlug();
            switch ($page) {
                case 'notifications':
               if (jvbSiteHasNotifications()) {
               if (Site::has('notifications')) {
                  wp_enqueue_script('jvb-notification-manager');
               }
                    break;
@@ -342,31 +543,37 @@
               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');
               break;
            default:
               wp_enqueue_script('jvb-crud');
               break;
            }
         if (jvbSiteHasFavourites()) {
         if (Site::has('favourites')) {
             wp_enqueue_script('jvb-favourites');
            wp_localize_script('jvb-favourites-manager', 'favouritesSettings', [
               'strings' => [
@@ -382,23 +589,61 @@
         wp_enqueue_script('jvb-creator');
         if (jvbSiteHasForum()) {
         if (Site::has('forum')) {
         wp_enqueue_script('jvb-news');
         }
         do_action('jvbDashScripts', $page);
        }
    }
    protected function getCurrentPage():string
   protected function getCurrentPageTitle():string
    {
      global $wp;
      $dash = str_replace('dash/', '', $wp->request);
      if (str_starts_with($dash, 'integrations/')) {
         $dash = str_replace('integrations/', '', $dash);
      if (is_post_type_archive(BASE.'dash')) {
         return 'dash';
      }
      return ($dash === '') ? 'dash' : $dash;
        global $post;
        if (!$post) {
            return '';
        }
        return html_entity_decode($post->post_title);
    }
   protected function getCurrentPageSlug():string
    {
      if (is_post_type_archive(BASE.'dash')) {
         return 'dash';
      }
        global $post;
        if (!$post) {
            return '';
        }
        return $post->post_name;
    }
   protected function getIcon(string $slug, string $page):string
   {
      return $this->cache->remember('icon_'.sanitize_title($page), function() use ($slug, $page) {
         $icon = sanitize_title($page);
         if (!is_numeric($slug)) {
            $registrar =Registrar::getInstance($slug);
            if ($registrar) {
            return $registrar->getIcon();
            }
         }
         return $icon;
      });
   }
   protected function getSlug(string $slug, string $page):string
   {
      return $this->cache->remember('slug_'.sanitize_title($page), function() use ($slug, $page) {
         if (!is_numeric($slug)) {
            return $slug;
         } else {
            return sanitize_title($page);
         }
      });
   }
    protected function renderHeader():void
    {
@@ -406,46 +651,47 @@
        <!DOCTYPE html>
    <html <?php language_attributes(); ?>>
        <head>
            <title><?= (array_key_exists('dashboard_title', JVB_SITE)) ? JVB_SITE['dashboard_title'] : 'Dashboard | '.get_bloginfo('name') ?></title>
            <title><?= Site::dashboardTitle()??'Dashboard | '.get_bloginfo('name') ?></title>
            <meta charset="<?php bloginfo('charset'); ?>">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <?php
         $pages = jvbGetUserDashboardPages();
         $pages = $this->getUserAllowedPages();
         foreach($pages as $page) {
            $page = str_replace('_', '-', $page);
            $link = ($page === 'dash') ? '/'.$page : "/dash/$page";
            ?>
            <link rel="preconnect" href="<?= get_home_url(2, $link)?>"/>
            <link rel="preconnect" href="<?= get_home_url(null, $link)?>"/>
            <?php
         }
          ?>
          <link rel="preconnect" href="<?= get_home_url()?>"/>
            <?php wp_head(); ?>
        </head>
    <body class="dashboard<?= ' '.$this->getCurrentPage()?>">
    <body class="dashboard<?= ' '.$this->getCurrentPageSlug()?>">
        <?php jvbAccessibility();?>
        <header>
            <?php
            $checked = (is_user_logged_in() && current_user_can('prefers_dark_theme', true)) ? ' checked' : '';
            $title = ($checked == '') ? 'Toggle Dark Mode' : 'Toggle Light Mode';
            echo '<label title="'.$title.'" id="theme-switch" class="toggle-switch" for="theme-switcher">
        <input class="theme-switch row" id="theme-switcher" type="checkbox"'.$checked.' role="switch" name="dark-mode"><span class="slider">'.
                 jvbIcon('light').
                 jvbIcon('dark').'</span></label>';
            ?>
            <p class="title">
                <a href="<?= get_home_url(); ?>" rel="home" title="Back to Site">
                    <?= jvbIcon('logo-basic'); ?>
                </a>
            </p>
         <?= 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() ?>
                </ul>
            </nav>
        </header>
         <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
@@ -455,45 +701,149 @@
    {
        ?>
        </section>
      <?php
      $menu = new Navigation('sidebar');
      $menuClasses = ['col', 'a-start', 'nowrap'];
      $itemClasses = ['col'];
      $menu->addClass('col a-start')->hasToggle()->defaultMenuClasses($menuClasses);
      $menu->defaultItemClasses($itemClasses);
      $pages = $this->getUserAllowedPages()?:[];
      //Dashboard
         //Referrals
      $dashboard = $menu->addItem('Dashboard',jvbDashIcon('door'))
         ->url($this->baseURL);
//       ->submenu('dashboard')
//       ->defaultMenuClasses($menuClasses)
//       ->defaultItemClasses($itemClasses);
      //notifications
      if (in_array('Notifications', $pages)) {
         $menu->addItem('Notifications',jvbDashIcon('bell'))
            ->url($this->baseURL.'/notifications');
      }
      if (in_array('Referrals', $pages)) {
         $menu->addItem('Referrals', jvbDashIcon('hand-heart'))
            ->url($this->baseURL.'/referrals');
      }
      if (in_array('Favourites', $pages)) {
         $menu->addItem('Favourites', jvbDashIcon('heart'))
            ->url($this->baseURL.'/favourites');
      }
      //Content
         //content types
      $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'))
            ->submenu('content')
            ->defaultMenuClasses($menuClasses)
            ->defaultItemClasses($itemClasses);
         foreach ($availableContent as $slug => $page) {
            $registrar = Registrar::getInstance($slug);
            $item = $content->addItem($page, $registrar->getIcon())
               ->url($this->baseURL.'/'.$slug);
            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);
                     if ($taxRegistrar) {
                        $itemMenu->addItem($taxRegistrar->getPlural(), $taxRegistrar->getIcon())
                        ->url($this->baseURL.'/'.$s);
                     }
                  }
               }
            }
         }
      }
      //Taxonomies
      //Settings
      $settings = $menu->addItem('Settings', jvbDashIcon('faders'))
         ->submenu('settings')
         ->defaultItemClasses($itemClasses)
         ->defaultMenuClasses($menuClasses);
         //SEO
         if (in_array('SEO', $pages)) {
            $settings->addItem('SEO', jvbDashIcon('robot'))
               ->url($this->baseURL.'/seo');
         }
         //Integrations
         if (in_array('Integrations', $pages)) {
            $settings->addItem('Integrations', jvbDashIcon('plugs-connected'))
               ->url($this->baseURL.'/integrations');
         }
      //Account
      $account = $menu->addItem('Account', jvbDashIcon('user-circle'))
         ->url($this->baseURL.'/account')
         ->submenu('account')
         ->defaultMenuClasses($menuClasses)
         ->defaultItemClasses($itemClasses);
      $account->addItem('Reset Password', jvbDashIcon('password'))
         ->url($this->baseURL.'/reset-password');
         //name + contact
         //reset password
         if (in_array('notifications', $pages)) {
            $account->addItem('Permissions', jvbDashIcon('keyhole'))
               ->url($this->baseURL.'/permissions');
         }
      echo $menu->render();
       ?>
        <footer class="col">
            <nav class="dashboard-nav">
         <?= jvbLoadingScreen() ?>
         <?= TaxonomySelector::outputSelectorModal() ?>
<!--            <nav class="dashboard-nav">-->
                <?php
                $current_page = $this->getCurrentPage();
                $pages = jvbGetUserDashboardPages()?:[];
            global $jvb_everything;
                echo '<ul>';
                foreach ($pages as $page) {
                    // Add data-page attribute for the navigator
                    $active = ($current_page == $page) ? ' class="current"' : '';
                    $current = ($current_page == $page) ? ' aria-current="page"' : '';
               $icon = (array_key_exists($page, $jvb_everything)) ? $jvb_everything[$page]['icon'] ?? $page : $page;
               $title = $this->getTitle($page);
               $page = str_replace('_', '-', $page);
               $link = ($page === 'dash') ? '/'.$page : "/dash/$page";
                    printf(
                        '<li%s><a href="%s"%s data-page="%s" data-dash title="%s">%s<span>%s</span></a></li>',
                        $active,
                        get_home_url(2, $link),
                        $current,
                        $page,
                        $title,
                        jvbIcon($icon, ['title'=> $title]),
                        $title
                    );
                }
                echo '</ul>';
//                $current_page = $this->getCurrentPageSlug();
//                $pages = $this->getUserAllowedPages()?:[];
//                echo '<ul>';
//                foreach ($pages as $slug => $page) {
//             $slug = $this->getSlug($slug, $page);
//             $icon = $this->getIcon($slug, $page);
//             // Add data-page attribute for the navigator
//                    $active = ($current_page == $slug) ? ' class="current"' : '';
//                    $current = ($current_page == $slug) ? ' aria-current="page"' : '';
//
//
//             $link = ($page === 'dash') ? '/'.$page : "/dash/$slug";
//                    printf(
//                        '<li%s><a href="%s"%s data-page="%s" data-dash title="%s">%s<span>%s</span></a></li>',
//                        $active,
//                        get_home_url(null, $link),
//                        $current,
//                        $slug,
//                        $page,
//                        jvbDashIcon($icon, ['title'=> $page]),
//                        $page
//                    );
//                }
//
//                echo '</ul>';
                ?>
            </nav>
<!--            </nav>-->
        </footer>
      <?php
        do_action('jvbRenderDashboardSettings', $this->getCurrentPage());
        do_action('jvbRenderDashboardSettings', $this->getCurrentPageSlug());
      ?>
        <?php wp_footer(); ?>
@@ -503,37 +853,50 @@
        <?php
    }
    protected function renderIndex():void
    public function renderIndex(string $content, string $page):string
    {
        $name = get_post_meta($this->userLink, BASE.'firstname', true);
        $name = ($name === '') ? $this->user->display_name : $name;
      if ($page !== '' && $page !== 'dash') {
         return $content;
      }
        echo '<h1 style="text-transform:none;margin-top:2em!important;">Hey '.$name.'</h1>';
      if (Site::has('referrals')) {
         $whatever = JVB()->referrals()->getReferralWelcomeMessage($this->user->ID);
         if (!empty($whatever)) {
            return $whatever;
         }
      }
      ob_start();
        $name = ($this->user->first_name !== '') ? $this->user->first_name : $this->user->display_name;
        echo '<h1>Hey '.$name.'</h1>';
        echo '<p>Welcome back!</p>';
        $pages = jvbGetUserDashboardPages();
        $pages = $this->getUserAllowedPages();
        echo '<h2>What would you like to do today?</h2>';
        global $jvb_everything;
        echo '<ul>';
        foreach ($pages as $page) {
        echo '<ul class="dashboard">';
        foreach ($pages as $slug => $page) {
         if ($page === 'dash') {
            continue;
         }
            $title = $this->getTitle($page);
            $url = sanitize_title($title);
            $description = $this->getDescription($page);
         $slug = $this->getSlug($slug, $page);
         $icon = $this->getIcon($slug, $page);
            if ($title !== '') {
                echo '<li><p><a href="'.get_home_url(2, '/dash/'.$url.'/').'"
                    data-page="'.$url.'" data-dash>'.jvbIcon($page).ucfirst($title).'</a></p>'.$description.'</li>';
                echo '<li><p><a href="'.get_home_url(null, '/dash/'.$slug.'/').'"
                    data-page="'.$slug.'" data-dash>'.jvbDashIcon($icon).ucwords($title).'</a></p></li>';
            }
        }
        echo '</ul>';
        echo '<p>Everything saves auto-magically, so rest easy.</p>';
      return ob_get_clean();
    }
    /**
     * Similar to CRUD, except it only manages a single item, such as a user's profile or a shop
@@ -543,10 +906,7 @@
     */
    protected function renderForm(string $type):void
    {
        if (!current_user_can('manage_'.$type)) {
            wp_redirect(get_home_url(2, '/dash'));
            exit;
        }
        wp_enqueue_script(
            'jvb-bio-manager',
            JVB_URL.'assets/js/min/bioManager.min.js',
@@ -562,10 +922,6 @@
    protected function renderSettings():void
    {
        if (!current_user_can('manage_options') && !current_user_can('manage_settings')) {
            wp_redirect(get_home_url(2, '/dash'));
            exit;
        }
      wp_enqueue_script('jvb-form');
        wp_enqueue_script(
            'jvb-bio-manager',
@@ -581,7 +937,7 @@
      if ($content !== '') {
         echo $content;
      } else {
      jvbRenderSections($this->user->ID, 'user', jvbUserRole());
         jvbRenderSections($this->user->ID, 'user', jvbUserRole());
      }
    }
@@ -593,14 +949,14 @@
      if (!empty($integrations)) {
         $out = '<nav class="integrations"><ul>';
         $url = get_home_url(2, '/dash/integrations/');
         $out .= '<li><a href="'.$url.'">'.jvbIcon('plugs-connected').'Integrations</a></li>';
         $url = get_home_url(null, '/dash/integrations/');
         $out .= '<li><a href="'.$url.'">'.jvbDashIcon('plugs-connected').'Integrations</a></li>';
         foreach ($integrations as $name=> $integration) {
            if (!JVB()->userCanConnect($name, $this->user->ID) || !$integration->hasDefaults()) {
               continue;
            }
            $link = sanitize_title(str_replace('_', '-',$name));
            $out .= '<li><a href="'.$url.$link.'">'.jvbIcon($integration->icon).$integration->getTitle().'</a></li>';
            $out .= '<li><a href="'.$url.$link.'">'.jvbDashIcon($integration->icon).$integration->getTitle().'</a></li>';
         }
         $out .= '</ul></nav>';
      }
@@ -609,12 +965,6 @@
   protected function renderIntegrations(string $page):void
   {
      //TODO: Make manage_integrations permission
//    if (!current_user_can('manage_integrations')) {
//       wp_redirect(get_home_url(2, '/dash'));
//       exit;
//    }
      echo $this->getIntegrationsMenu();
      $map = [
         'google-my-business' => 'gmb',
@@ -623,7 +973,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>';
@@ -653,24 +1003,20 @@
    protected function renderApprovals():void
    {
        if (!current_user_can('skip_moderation')) {
            wp_redirect(get_home_url(2, '/dash'));
            exit;
        }
        ?>
        <div class="approvals container">
            <nav class="tabs row start" role="tablist">
                <button type="button" class="tab active" data-tab="summary" role="tab" aria-selected="true">
                    <h2><?= jvbIcon('all')?>All</h2>
                   <?= jvbDashIcon('infinity')?>All
                </button>
                <button type="button" class="tab" data-tab="artists" role="tab" aria-selected="false">
                    <h2><?= jvbIcon('artists')?>Artists</h2>
                    <?= jvbDashIcon('users-three')?>Artists
                </button>
                <button type="button" class="tab" data-tab="terms" role="tab" aria-selected="false">
                    <h2><?= jvbIcon('style')?>Terms</h2>
                   <?= jvbDashIcon('hash')?>Terms
                </button>
                <button type="button" class="tab" data-tab="yours" role="tab" aria-selected="false">
                    <h2><?= jvbIcon('artist')?>Yours</h2>
                    <?= jvbDashIcon('user')?>Yours
                </button>
            </nav>
        </div>
@@ -723,50 +1069,30 @@
    }
    protected function renderCRUD(string $type):void
    public function renderAdmin(string $content, string $page):string
    {
      $type = match($type) {
         'menu-item' => 'menu_item',
         'events' => 'event',
         default => $type
      };
      $permission = JVB_CONTENT[$type]['plural']??$type.'s';
        if (!current_user_can('edit_'.$permission)) {
            wp_redirect(get_home_url(2, '/dash'));
            exit;
        }
        $crud = new CRUD($type);
        $crud->render();
    }
    protected function renderAdmin():void
    {
        //TODO: This has to be built from the settings from setup.php
        if (!current_user_can('manage_options')) {
            wp_redirect(get_home_url(2, '/dash'));
            exit;
        }
      if ($page !== '' && $page !== 'dash') {
         return $content;
      }
      ob_start();
        ?>
        <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><?=jvbIcon($type)?> <?= $settings['plural'] ?></h2>
                <h2><?=jvbDashIcon($registrar->getIcon())?> <?= $registrar->getPlural() ?></h2>
            </button>
            <?php
            $i++;
@@ -777,8 +1103,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>
@@ -793,19 +1120,19 @@
            'vertical',
            'TAB NAV:',
            '',
            jvbIcon('down'),
            jvbIcon('right'))?>
            jvbDashIcon('caret-double-down'),
            jvbDashIcon('caret-double-right'))?>
    </div>
    <div class="items-container">
    </div>
    <?php
    global $jvb_everything;
    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>
@@ -852,18 +1179,18 @@
        <template class="<?= $type ?>Row">
            <tr>
                <td>
                     <?= jvbIcon('grab') ?>
                     <?= jvbDashIcon('dots-six-vertical') ?>
                 </td>
                 <td data-id="actions" class="col">
                     <?= jvbRenderToggleTextField(
                         'public',
                         '',
                         '',
                         jvbIcon('show'),
                         jvbIcon('hide'))
                         jvbDashIcon('eye'),
                         jvbDashIcon('eye-closed'))
                     ?>
                     <button type="button" data-action="edit">
                         <?= jvbIcon('edit') ?>
                         <?= jvbDashIcon('pencil-simple') ?>
                    </button>
                </td>
                <?php
@@ -874,7 +1201,7 @@
                            <?php
                            $config['type'] = 'text';
                            $config['description'] = '';
                            $meta->render('form', $n, $config);
                            Form::render($n, null, $config);
                            ?>
                        </td>
                        <?php
@@ -889,8 +1216,310 @@
        echo jvbNewModal(
            'edit-modal '.$type,
            'Edit '.ucfirst($type),
            $meta->renderForm('admin', [], $fields)
            jvbRenderForm('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 = [];
         $pages[] = 'SEO';
         // Add feature-dependent pages (non-config)
         if (Site::has('referrals')) {
            $pages[] = 'Referrals';
         }
         $membership = Site::membership();
         if ($membership && $membership->has('can_invite')) {
            $pages[] = 'Invites';
         }
         if ($membership && $membership->has('term_approval')) {
            $pages[] = 'Approvals';
         }
         if ($membership && $membership->has('forum')) {
            $pages[] = 'News';
         }
         if ($membership && $membership->has('member_content')) {
            $pages[] = 'Metrics';
         }
         if (Site::has('favourites')) {
            $pages[] = 'Favourites';
         }
         if (!empty(Registrar::getFeatured('karma'))) {
            $pages[] = 'Karmic Score';
         }
         if (Site::has('notifications')) {
            $pages[] = 'Notifications';
         }
         if (Site::has('support')) {
            $pages[] = 'Support';
         }
         if (Site::hasAnyIntegration()) {
            $pages[] = 'Integrations';
         }
         // Add all content types (with config keys)
         foreach (Registrar::getRegistered('post') as $slug) {
            $registrar = Registrar::getInstance($slug);
            $pages[$slug] = $registrar->getPlural();
         }
         foreach (Registrar::getRegistered('term') as $slug) {
            $registrar = Registrar::getInstance($slug);
            $pages[$slug] = $registrar->getPlural();
         }
         // 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 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 [];
      }
      $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($userID, $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)) {
               $registrar = Registrar::getInstance($key);
               $membership = Site::membership();
               $type  = $registrar? $registrar->getType() : false;
               if ($type) {
                  $permission = JVB()->roles()->getPermission('edit', $key);
               }
               switch ($type) {
                  case 'content':
                     if (user_can($userID, $permission)) {
                        $remove = false;
                     }
                     break;
                  case 'taxonomy':
                     $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($registrar->registrar->for, array_keys($pages))) > 0) {
                        $remove = false;
                     }
                     break;
               }
            } else {
               switch ($slug) {
                  case 'Integrations':
                     foreach($roles as $role) {
                        if (Registrar::getInstance($role)->hasAnyIntegrations()) {
                           $remove = false;
                        }
                     }
                     break;
                  case 'invites':
                     $canInvite = JVB_MEMBERSHIP['can_invite']??[];
                     foreach ($roles as $role) {
                        if (array_key_exists($role, $canInvite)) {
                           $remove = false;
                        }
                     }
                     if ($remove) {
                        //TODO: Figure out what $config was supposed to be
                        if ($canSkip || ($registrar && $registrar->hasFeature('invitable'))) {
                           $remove = false;
                        }
                     }
                     break;
                  case 'Approvals':
                     $canApprove = false;
                     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'])) {
                                 $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(Registrar::getInstance($role)?->getCreatable())) {
                           $remove = false;
                        }
                     }
                     break;
                  case 'karmic-score':
                     foreach ($roles as $role) {
                        $contents = Registrar::getInstance($role)?->getCreatable();
                        if (!empty($contents)) {
                           $hasKarma = Registrar::getFeatured('karma');
                           $remove = empty(array_intersect($contents, $hasKarma));
                        }
                     }
                     break;
                  case 'dash':
                  case 'Referrals':
                  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($userID, $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 = Registrar::getInstance($role)?->getCreatable();
         $creatable = array_merge($creatable, $roleCreatable);
      }
      return !empty($creatable);
   }
   /**
    * Get user roles that have dashboard access
    * Replaces jvbRolesWithDashboard()
    * @return array
    */
   protected function getRolesWithDashboard():array
   {
      return Registrar::getFeatured('has_dashboard', 'user');
   }
   /**
    * 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;
   }
}