config = $config; $this->type = $type; $this->slug = $slug; } /** * Create from a specific content type */ public static function forContent(string $slug): self { $slug = jvbNoBase($slug); if (!isset(JVB_CONTENT[$slug])) { return new self([], 'content', $slug); } return new self(JVB_CONTENT[$slug], 'content', $slug); } public static function hasIntegration(string $integration, 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': $feature = (array_key_exists('integrations', JVB_SITE)) ? new self(JVB_SITE['integrations'], 'integrations', 'site-integrations') : new self([], 'integrations', 'site-integrations'); break; case 'content': $feature = (!isset(JVB_CONTENT[$subType])|| !array_key_exists('integrations', JVB_CONTENT[$subType])) ? new self([], 'integrations', 'content-integrations') : new self(JVB_CONTENT[$subType]['integrations'], 'integrations', 'content-integrations'); break; case 'taxonomy': $feature = (!isset(JVB_TAXONOMY[$subType])|| !array_key_exists('integrations', JVB_TAXONOMY[$subType])) ? new self([], 'integrations', 'taxonomy-integrations') : new self(JVB_TAXONOMY[$subType]['integrations'], 'integrations', 'taxonomy-integrations'); break; case 'user': $feature = (!isset(JVB_USER[$subType])|| !array_key_exists('integrations', JVB_USER[$subType])) ? new self([], 'integrations', 'user-integrations') : new self(JVB_USER[$subType]['integrations'], 'integrations', 'user-integrations'); break; default: return false; } 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 */ public static function forTaxonomy(string $slug): self { $slug = jvbNoBase($slug); if (!isset(JVB_TAXONOMY[$slug])) { return new self([], 'taxonomy', $slug); } return new self(JVB_TAXONOMY[$slug], 'taxonomy', $slug); } /** * Create from a specific user role */ public static function forUser(string $slug): self { $slug = jvbNoBase($slug); if (!isset(JVB_USER[$slug])) { return new self([], 'user', $slug); } return new self(JVB_USER[$slug], 'user', $slug); } /** * Create for site-wide features */ public static function forSite(): self { return new self(JVB_SITE ?? [], 'site', 'site'); } /** * Create for membership features */ public static function forMembership(): self { return new self(JVB_MEMBERSHIP ?? [], 'membership', 'membership'); } /** * Check if a single feature is enabled */ public function has(string $feature): bool { // Check cache first $cacheKey = $this->getCacheKey($feature); if (isset($this->cache[$cacheKey])) { return $this->cache[$cacheKey]; } // Perform the check $result = isset($this->config[$feature]) && $this->config[$feature] === true; // Cache the result $this->cache[$cacheKey] = $result; return $result; } /** * Check if ALL specified features are enabled */ public function hasAll(array $features): bool { foreach ($features as $feature) { if (!$this->has($feature)) { return false; } } return true; } /** * Check if ANY of the specified features are enabled */ public function hasAny(array $features): bool { foreach ($features as $feature) { if ($this->has($feature)) { return true; } } return false; } /** * Check if NONE of the specified features are enabled */ public function hasNone(array $features): bool { return !$this->hasAny($features); } /** * Get all enabled features */ public function getEnabled(): array { $enabled = []; // Determine which features to check based on type $featuresToCheck = $this->getFeatureList(); foreach ($featuresToCheck as $feature) { if ($this->has($feature)) { $enabled[] = $feature; } } return $enabled; } /** * Get all disabled features */ public function getDisabled(): array { $disabled = []; $featuresToCheck = $this->getFeatureList(); foreach ($featuresToCheck as $feature) { if (!$this->has($feature)) { $disabled[] = $feature; } } return $disabled; } /** * Check feature with a default value if not set */ public function get(string $feature, bool $default = false): bool { if (!isset($this->config[$feature])) { return $default; } return $this->has($feature); } /** * Check if a feature is explicitly set (regardless of value) */ public function isSet(string $feature): bool { return isset($this->config[$feature]); } /** * Get the raw value of a feature (not just boolean) */ public function getValue(string $feature, $default = null) { return $this->config[$feature] ?? $default; } /** * Check features with dependencies */ public function hasWithDependencies(string $feature): bool { if (!$this->has($feature)) { return false; } // Check dependencies based on feature $dependencies = $this->getFeatureDependencies($feature); return $this->hasAll($dependencies); } /** * Global feature checks across all types */ public static function anyContentHas(string $feature): bool { $cacheKey = "content_any_{$feature}"; 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)) { self::$globalCache[$cacheKey] = true; return true; } } self::$globalCache[$cacheKey] = false; return false; } /** * Check if any taxonomy has a feature */ public static function anyTaxonomyHas(string $feature): bool { $cacheKey = "taxonomy_any_{$feature}"; if (isset(self::$globalCache[$cacheKey])) { return self::$globalCache[$cacheKey]; } foreach (JVB_TAXONOMY as $slug => $config) { $flags = new self($config, 'taxonomy', $slug); if ($flags->has($feature)) { self::$globalCache[$cacheKey] = true; return true; } } self::$globalCache[$cacheKey] = false; return false; } /** * Check if any user role has a feature */ public static function anyUserHas(string $feature): bool { $cacheKey = "user_any_{$feature}"; if (isset(self::$globalCache[$cacheKey])) { return self::$globalCache[$cacheKey]; } foreach (JVB_USER as $slug => $config) { $flags = new self($config, 'user', $slug); if ($flags->has($feature)) { self::$globalCache[$cacheKey] = true; return true; } } self::$globalCache[$cacheKey] = false; return false; } /** * Get all types with a specific feature */ public static function getTypesWithFeature(string $feature, string $type = 'content'): array { $types = []; $source = match ($type) { 'content' => JVB_CONTENT, 'taxonomy' => JVB_TAXONOMY, 'user' => JVB_USER, default => [] }; foreach ($source as $slug => $config) { $flags = new self($config, $type, $slug); if ($flags->has($feature)) { $types[] = $slug; } } return $types; } /** * Count types with a feature */ public static function countWithFeature(string $feature, string $type = 'content'): int { return count(self::getTypesWithFeature($feature, $type)); } /** * Check complex feature combinations */ public function meetsRequirements(array $requirements): bool { foreach ($requirements as $requirement => $expected) { if (is_array($expected)) { // Handle OR conditions if (!$this->hasAny($expected)) { return false; } } elseif (is_bool($expected)) { // Handle boolean requirements if ($this->has($requirement) !== $expected) { return false; } } elseif (is_callable($expected)) { // Handle custom validation if (!$expected($this->getValue($requirement))) { return false; } } } return true; } /** * Get feature statistics */ public function getStats(): array { $all = $this->getFeatureList(); $enabled = $this->getEnabled(); return [ 'total' => count($all), 'enabled' => count($enabled), 'disabled' => count($all) - count($enabled), 'percentage' => count($all) > 0 ? round((count($enabled) / count($all)) * 100, 2) : 0 ]; } /** * Export configuration for debugging */ public function export(): array { return [ 'type' => $this->type, 'slug' => $this->slug, 'features' => $this->getEnabled(), 'config' => $this->config ]; } /** * Check if configuration supports a specific workflow */ public function supportsWorkflow(string $workflow): bool { $workflows = [ 'moderation' => ['approve_new', 'member_verified'], 'social' => ['karma', 'responses', 'favouritable'], 'calendar' => ['is_calendar'], 'directory' => ['show_directory'], 'ownership' => ['is_ownable', 'is_owned_by'], 'dashboard' => ['has_dashboard'], 'public_profile' => ['has_dashboard', 'profile'], 'content_creation' => ['can_create'], 'invitation' => ['invitable', 'can_invite'], 'feed' => ['show_feed', 'use_feed_block'], 'ecommerce' => ['square', 'syncWithSquare'], 'analytics' => ['keep_stats', 'umami'], 'forum' => ['forum', 'responses'] ]; if (!isset($workflows[$workflow])) { return false; } return $this->hasAny($workflows[$workflow]); } /** * Get feature dependencies */ private function getFeatureDependencies(string $feature): array { $dependencies = [ 'responses' => ['forum'], 'karma' => ['member_verified'], 'invitable' => ['can_invite'], 'keep_stats' => ['has_dashboard'], 'syncWithSquare' => ['square'] ]; return $dependencies[$feature] ?? []; } /** * Get list of features based on type */ private function getFeatureList(): array { return match ($this->type) { 'content' => self::CONTENT_FEATURES, 'taxonomy' => self::TAXONOMY_FEATURES, 'user' => self::USER_FEATURES, 'site' => self::SITE_FEATURES, 'membership' => array_keys($this->config), default => array_keys($this->config) }; } /** * Generate cache key */ private function getCacheKey(string $feature): string { return "{$this->type}_{$this->slug}_{$feature}"; } /** * Clear cache */ public function clearCache(): void { $this->cache = []; } /** * Clear global cache */ public static function clearGlobalCache(): void { self::$globalCache = []; } /** * Magic method for property-style access */ public function __get(string $name): bool { return $this->has($name); } /** * Magic method for method-style checks */ public function __call(string $name, array $arguments): bool { // Support is*, has*, can* method calls if (preg_match('/^(is|has|can)(.+)$/', $name, $matches)) { $feature = lcfirst($matches[2]); // Convert camelCase to snake_case $feature = strtolower(preg_replace('/([A-Z])/', '_$1', $feature)); return $this->has($feature); } throw new \BadMethodCallException("Method {$name} does not exist"); } }