Jake Vanderwerf
2026-01-04 d38d825e3484d822ea3c1f0fb1df37ecf386b18a
inc/utility/Features.php
@@ -22,7 +22,7 @@
   const CONTENT_FEATURES = [
      'hide_single', 'show_feed', 'show_directory', 'karma',
      'favouritable', 'responses', 'is_calendar', 'single_image',
      'redirectToAuthor', 'syncWithSquare', 'approve_new'
      'redirectToAuthor', 'syncWithSquare', 'approve_new', 'is_gallery'
   ];
   const TAXONOMY_FEATURES = [
@@ -297,7 +297,6 @@
      if (isset(self::$globalCache[$cacheKey])) {
         return self::$globalCache[$cacheKey];
      }
      foreach (JVB_CONTENT as $slug => $config) {
         $flags = new self($config, 'content', $slug);
         if ($flags->has($feature)) {
@@ -503,6 +502,285 @@
      };
   }
   /*****************************************************************
    * Dashboard Utilitiies
   *****************************************************************/
   /**
    * Get content types that a user role can create
    * Extracts and flattens from 'can_create' config
    *
    * @return array Array of content type slugs
    *
    * Usage:
    * Features::forUser('artist')->getCreatableContent()
    * // Returns: ['tattoo', 'piercing', 'artwork']
    */
   public function getCreatableContent(): array
   {
      if ($this->type !== 'user') {
         return [];
      }
      $canCreate = $this->getValue('can_create', []);
      if (empty($canCreate)) {
         return [];
      }
      $content = [];
      foreach ($canCreate as $item) {
         if (is_array($item)) {
            // Handle nested arrays like ['shop' => ['tattoo', 'piercing']]
            foreach ($item as $type => $contents) {
               $content = array_merge($content, $contents);
            }
         } else {
            // Handle simple strings
            $content[] = $item;
         }
      }
      return array_unique($content);
   }
   /**
    * Get all dashboard pages for a user role
    * Includes profile, creatable content, and settings
    *
    * @return array Array of page slugs
    *
    * Usage:
    * Features::forUser('artist')->getDashboardPages()
    * // Returns: ['artist-profile', 'tattoo', 'piercing', 'settings']
    */
   public function getDashboardPages(): array
   {
      if ($this->type !== 'user') {
         return [];
      }
      $pages = [];
      // Add profile page if configured
      $profile = $this->getValue('profile');
      if ($profile) {
         $pages[] = $profile;
      }
      // Add creatable content types
      $pages = array_merge($pages, $this->getCreatableContent());
      // Add settings if user has dashboard
      if ($this->has('has_dashboard')) {
         $pages[] = 'settings';
      }
      return array_unique($pages);
   }
   /**
    * Check if user role can create a specific content type
    *
    * @param string $contentType
    * @return bool
    *
    * Usage:
    * Features::forUser('artist')->canCreate('tattoo') // true/false
    */
   public function canCreate(string $contentType): bool
   {
      return in_array($contentType, $this->getCreatableContent());
   }
   /**
    * Get the profile type for a user role
    *
    * @return string|null Profile slug or null if none
    *
    * Usage:
    * Features::forUser('artist')->getProfile() // 'artist-profile'
    */
   public function getProfile(): ?string
   {
      if ($this->type !== 'user') {
         return null;
      }
      return $this->getValue('profile');
   }
   /**
    * Check if user role has a profile page
    *
    * @return bool
    *
    * Usage:
    * Features::forUser('artist')->hasProfile() // true/false
    */
   public function hasProfile(): bool
   {
      return $this->getProfile() !== null;
   }
   /**
    * Get content types grouped by parent type (if nested)
    *
    * @return array Associative array with parent types as keys
    *
    * Usage:
    * Features::forUser('artist')->getGroupedContent()
    * // Returns: ['shop' => ['tattoo', 'piercing'], 'standalone' => ['artwork']]
    */
   public function getGroupedContent(): array
   {
      if ($this->type !== 'user') {
         return [];
      }
      $canCreate = $this->getValue('can_create', []);
      if (empty($canCreate)) {
         return [];
      }
      $grouped = [];
      foreach ($canCreate as $item) {
         if (is_array($item)) {
            // Handle nested arrays like ['shop' => ['tattoo', 'piercing']]
            foreach ($item as $parent => $contents) {
               if (!isset($grouped[$parent])) {
                  $grouped[$parent] = [];
               }
               $grouped[$parent] = array_merge($grouped[$parent], $contents);
            }
         } else {
            // Handle simple strings - add to 'standalone'
            if (!isset($grouped['standalone'])) {
               $grouped['standalone'] = [];
            }
            $grouped['standalone'][] = $item;
         }
      }
      return $grouped;
   }
   /**
    * Static method to get all content types across all user roles
    *
    * @return array Array of unique content type slugs
    *
    * Usage:
    * Features::getAllUserContent()
    * // Returns: ['tattoo', 'piercing', 'artwork', 'event', ...]
    */
   public static function getAllUserContent(): array
   {
      $allContent = [];
      foreach (JVB_USER as $slug => $config) {
         $features = self::forUser($slug);
         $allContent = array_merge($allContent, $features->getCreatableContent());
      }
      return array_unique($allContent);
   }
   /**
    * Static method to get all user roles that can create specific content
    *
    * @param string $contentType
    * @return array Array of role slugs
    *
    * Usage:
    * Features::getRolesForContent('tattoo')
    * // Returns: ['artist', 'shop']
    */
   public static function getRolesForContent(string $contentType): array
   {
      $roles = [];
      foreach (JVB_USER as $slug => $config) {
         $features = self::forUser($slug);
         if ($features->canCreate($contentType)) {
            $roles[] = $slug;
         }
      }
      return $roles;
   }
   /**
    * Get all dashboard pages across all user roles
    *
    * @return array Array of unique page slugs
    *
    * Usage:
    * Features::getAllDashboardPages()
    * // Returns: ['artist-profile', 'shop-profile', 'tattoo', 'piercing', ...]
    */
   public static function getAllDashboardPages(): array
   {
      $allPages = [];
      foreach (JVB_USER as $slug => $config) {
         $features = self::forUser($slug);
         $allPages = array_merge($allPages, $features->getDashboardPages());
      }
      return array_unique($allPages);
   }
   public static function getType(string $slug):?string
   {
      if (array_key_exists($slug, JVB_CONTENT)) {
         return 'content';
      }
      if (array_key_exists($slug, JVB_USER)) {
         return 'user';
      }
      if (array_key_exists($slug, JVB_TAXONOMY)) {
         return 'taxonomy';
      }
      if (array_key_exists($slug, JVB_OPTIONS)) {
         return 'option';
      }
      return null;
   }
   public static function getConfig(string $slug, ?string $type = null): array
   {
      $slug = jvbNoBase($slug);
      $all = ['post', 'content', 'taxonomy', 'user'];
      $types = (!$type || !in_array($type, $all)) ? $all : [$type];
      foreach($types as $type) {
         switch($type) {
            case 'post':
            case 'content':
               if (array_key_exists($slug, JVB_CONTENT)) {
                  return JVB_CONTENT[$slug];
               }
               break;
            case 'taxonomy':
               if (array_key_exists($slug, JVB_TAXONOMY)) {
                  return JVB_TAXONOMY[$slug];
               }
               break;
            case 'user':
               if (array_key_exists($slug, JVB_USER)) {
                  return JVB_USER[$slug];
               }
               break;
            default:
               return [];
         }
      }
      error_log('No config found for: '.$slug);
      return [];
   }
   /**
    * Generate cache key
    */