From 52733beffd7f1c48012b371d4ad8e7d937afd924 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Thu, 01 Jan 2026 23:21:25 +0000
Subject: [PATCH] Merge branch 'main' of https://github.com/jakevdwerf/jvb

---
 inc/utility/Features.php |  305 ++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 303 insertions(+), 2 deletions(-)

diff --git a/inc/utility/Features.php b/inc/utility/Features.php
index c0ae652..422bad3 100644
--- a/inc/utility/Features.php
+++ b/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,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
 	 */

--
Gitblit v1.10.0