Jake Vanderwerf
2025-11-04 42fa8304ddb811b0f725f245130f70c0f5e86a6c
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 = [
@@ -92,6 +92,29 @@
      }
      return $feature->has($integration);
   }
   public static function hasAnyIntegration(string $type = 'site', ?string $subType = null):bool
   {
      $allowedTypes = ['site', 'content', 'taxonomy', 'user'];
      if (!in_array($type, $allowedTypes)) {
         return false;
      }
      if (in_array($type, ['content', 'taxonomy', 'user']) && !$subType) {
         return false;
      }
      switch ($type) {
         case 'site':
            return (array_key_exists('integrations', JVB_SITE) && !empty(JVB_SITE['integrations']));
         case 'content':
            return (array_key_exists($subType, JVB_CONTENT) && array_key_exists('integrations', JVB_CONTENT[$subType]) && !empty(JVB_CONTENT[$subType]['integrations']));
         case 'taxonomy':
            return (array_key_exists($subType, JVB_TAXONOMY) && array_key_exists('integrations', JVB_TAXONOMY[$subType]) && !empty(JVB_TAXONOMY[$subType]['integrations']));
         case 'user':
            return (array_key_exists($subType, JVB_USER) && array_key_exists('integrations', JVB_USER[$subType]) && !empty(JVB_USER[$subType]['integrations']));
         default:
            return false;
      }
   }
   /**
    * Create from a specific taxonomy
    */
@@ -274,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)) {
@@ -480,6 +502,284 @@
      };
   }
   /*****************************************************************
    * 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
   {
      $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
    */