From 83985b1f1534d70cca59edb01627638c890116cb Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Mon, 09 Feb 2026 01:36:09 +0000
Subject: [PATCH] =Schema Updates

---
 inc/managers/SEO/SchemaOutputManager.php |  212 ++++++++++------------------------------------------
 1 files changed, 43 insertions(+), 169 deletions(-)

diff --git a/inc/managers/SEO/SchemaOutputManager.php b/inc/managers/SEO/SchemaOutputManager.php
index 140716b..f06ab10 100644
--- a/inc/managers/SEO/SchemaOutputManager.php
+++ b/inc/managers/SEO/SchemaOutputManager.php
@@ -1,8 +1,10 @@
 <?php
 namespace JVBase\managers\SEO;
 
-use JVBase\managers\CacheManager;
-use JVBase\meta\MetaManager;
+use JVBase\managers\Cache;
+use JVBase\managers\SEO\schemas\SchemaResolverRegistry;
+use JVBase\meta\Meta;
+use JVBase\managers\SEO\schemas\SchemaDefinition;
 use WP_Term;
 use WP_User;
 
@@ -16,14 +18,14 @@
  * Integrates with The SEO Framework, letting it handle defaults
  * while we override with our configured templates.
  *
- * Now with integrated caching via CacheManager for performance.
+ * Now with integrated caching via Cache for performance.
  */
 class SchemaOutputManager
 {
 	private ConfigManager $config;
 	private SchemaBuilder $registry;
 	private ?TemplateResolver $resolver = null;
-	private CacheManager $cache;
+	private Cache $cache;
 	private array $pseudoTypes = [
 		'BeforeAfter',
 	];
@@ -31,12 +33,10 @@
 	public function __construct()
 	{
 		$this->registry = SchemaBuilder::getInstance();
-		$this->cache = CacheManager::for('schema');
-
-		// Register cache connections
-		$this->cache->connectTo('post', 'id');
-		$this->cache->connectTo('taxonomy', 'id');
-		$this->cache->connectTo('user', 'id');
+		$this->cache = Cache::for('schema')
+			->connect('post',true)
+			->connect('taxonomy',true)
+			->connect('user',true);
 
 		// Hook into TSF for meta
 		add_filter('the_seo_framework_title_from_generation', [$this, 'filterTitle'], 10, 2);
@@ -132,12 +132,12 @@
 
 		$metaConfig = $this->config->meta();
 
-		if (empty($metaConfig['title'])) {
+		if (empty($metaConfig['metaTitle'])) {
 			return $title;
 		}
 
 		$resolver = $this->getResolver();
-		$customTitle = $resolver->resolve($metaConfig['title']);
+		$customTitle = $resolver->resolve($metaConfig['metaTitle']);
 
 		return $customTitle ?: $title;
 	}
@@ -158,12 +158,12 @@
 
 		$metaConfig = $this->config->meta();
 
-		if (empty($metaConfig['description'])) {
+		if (empty($metaConfig['metaDescription'])) {
 			return $description;
 		}
 
 		$resolver = $this->getResolver();
-		$customDescription = $resolver->resolve($metaConfig['description']);
+		$customDescription = $resolver->resolve($metaConfig['metaDescription']);
 
 		// Truncate to reasonable length
 		if (strlen($customDescription) > 160) {
@@ -190,16 +190,16 @@
 		$metaConfig = $this->config->meta();
 
 		// Check for custom image
-		if (!empty($metaConfig['image'])) {
+		if (!empty($metaConfig['socialPreviewImage'])) {
 			$resolver = $this->getResolver();
-			$imageUrl = $resolver->resolve($metaConfig['image']);
+			$imageUrl = $resolver->resolve($metaConfig['socialPreviewImage']);
 
 			if ($imageUrl) {
 				$params['og:image'] = $imageUrl;
 
 				// Use twitter-specific image if set, otherwise use main image
-				if (!empty($metaConfig['twitter_image'])) {
-					$twitterImage = $resolver->resolve($metaConfig['twitter_image']);
+				if (!empty($metaConfig['twitterImage'])) {
+					$twitterImage = $resolver->resolve($metaConfig['twitterImage']);
 					$params['twitter:image'] = $twitterImage ?: $imageUrl;
 				} else {
 					$params['twitter:image'] = $imageUrl;
@@ -386,10 +386,10 @@
 		$resolver = $this->getResolver();
 		$schemaType = $schemaConfig['type'];
 
-		// Resolve all field values from templates
+		// Resolve templates (resolver handles transformation)
 		$resolvedConfig = $this->resolveConfigTemplates($schemaConfig, $resolver);
 
-		// Build schema with resolved values
+		// Build via resolver system
 		$schema = $this->buildSchemaFromConfig(
 			$resolvedConfig,
 			$schemaType,
@@ -407,96 +407,30 @@
 		return $schema;
 	}
 	/**
-	 * Build schema for archive pages
-	 * Automatically generates mainEntity from archive posts
+	 * Build schema for archive pages.
+	 * mainEntity is now handled by CollectionPageResolver::getAutoFields()
 	 */
 	private function buildArchiveSchema(array $context): ?array
 	{
-		// Ensure archive config is initialized
 		if (!$this->config->archive()) {
 			$this->config->setupArchive();
 		}
 
 		$archiveConfig = $this->config->archive();
 
-		// Return null if no config or no type defined
 		if (empty($archiveConfig) || empty($archiveConfig['type'])) {
 			return null;
 		}
 
 		$resolver = $this->getResolver();
-		$schemaType = $archiveConfig['type'];
-
-		// Resolve templates from archive config
 		$resolvedConfig = $this->resolveConfigTemplates($archiveConfig, $resolver);
 
-		// Build base schema
-		$schema = $this->buildSchemaFromConfig(
+		// Resolver handles mainEntity auto-enrichment now
+		return $this->buildSchemaFromConfig(
 			$resolvedConfig,
-			$schemaType,
-			$resolver->resolveVariable('permalink') . '#' . strtolower($schemaType)
+			$archiveConfig['type'],
+			$resolver->resolveVariable('permalink') . '#' . strtolower($archiveConfig['type'])
 		);
-
-		if (!$schema) {
-			return null;
-		}
-
-		// Automatically add mainEntity for types that need it
-		$mainEntity = $this->buildMainEntity($schemaType, $context['type']);
-		if ($mainEntity) {
-			$schema['mainEntity'] = $mainEntity;
-		}
-
-		return $schema;
-	}
-
-	/**
-	 * Automatically build mainEntity for archive pages
-	 * Uses SchemaReferenceBuilder to generate entities from archive posts
-	 *
-	 * @param string $archiveSchemaType The archive's @type (FAQPage, CollectionPage, etc.)
-	 * @param string $contentType The content type being archived (faq, artwork, etc.)
-	 * @return array|null Array of entities or null if not applicable
-	 */
-	private function buildMainEntity(string $archiveSchemaType, string $contentType): ?array
-	{
-		// Only certain archive types need mainEntity
-		$typesNeedingMainEntity = ['FAQPage', 'CollectionPage', 'ItemList'];
-		if (!in_array($archiveSchemaType, $typesNeedingMainEntity)) {
-			return null;
-		}
-
-		$context = $this->getCurrentContext();
-
-		// For taxonomy term archives, get posts from the term
-		if ($context['objectType'] === 'term') {
-			// Get the post type(s) this taxonomy is for
-			$taxonomy = defined('JVB_TAXONOMY') && isset(JVB_TAXONOMY[$contentType])
-				? JVB_TAXONOMY[$contentType]
-				: null;
-
-			if (!$taxonomy || empty($taxonomy['for_content'])) {
-				return null;
-			}
-
-			// Use the first post type (most common case)
-			$postType = $taxonomy['for_content'][0];
-
-			return SchemaReferenceBuilder::buildFromTerm(
-				$context['objectId'],
-				$postType,
-				10,  // limit
-				null, // auto-infer type
-				true  // include context
-			);
-		}
-
-		// For post type archives
-		if ($context['objectType'] === 'archive') {
-			return SchemaReferenceBuilder::buildFromArchive($contentType);
-		}
-
-		return null;
 	}
 
 	/**
@@ -522,86 +456,26 @@
 	}
 
 	/**
-	 * Enhanced buildSchemaFromConfig with MetaManager integration
+	 * Build schema from config using the resolver system.
+	 *
+	 * Replaces the old double-transform approach with a single-pass
+	 * resolver that handles template resolution and transformation.
 	 */
 	private function buildSchemaFromConfig(array $config, string $schemaType, ?string $id = null): ?array
 	{
-		// Build base schema
-		$schema = ['@type' => $this->resolveSchemaType($schemaType)];
-
-		if ($id) {
-			$schema['@id'] = $id;
-		}
-
-		// Get MetaManager if we have a context
-		$meta = null;
 		$context = $this->getCurrentContext();
-		if ($context) {
-			$meta = new MetaManager($context['objectId'], $context['objectType']);
-		}
 
-		// Process each field
-		foreach ($config as $fieldName => $value) {
-			// Skip meta fields and empty values
-			if ($fieldName === 'type' || $value === null || $value === '' || $value === []) {
-				continue;
-			}
+		$definition = SchemaDefinition::fromContext(
+			$this->resolveSchemaType($schemaType),
+			$config,
+			$id,
+			$context
+		);
 
-			// Auto-resolve field value (handles images, locations, etc.)
-			$value = SchemaFieldHelpers::autoResolve($fieldName, $value, $meta);
+		$resolver = SchemaResolverRegistry::getInstance()->get($schemaType);
+		$meta = $context ? new Meta($context['objectId'], $context['objectType']) : null;
 
-			// Get field definition for transformer
-			$fieldDef = $this->registry->getFieldDefinition($fieldName);
-
-			// Apply transformer if defined
-			if ($fieldDef && !empty($fieldDef['transformer'])) {
-				$value = $this->applyTransformer($value, $fieldDef['transformer'], $fieldName);
-			}
-
-			// Skip if empty after transformation
-			if ($value === null || $value === '' || $value === []) {
-				continue;
-			}
-
-			// Handle multi-property transformers (like location_complex returns address + geo)
-			if (is_array($value) && !isset($value['@type']) && !isset($value[0])) {
-				$multiProps = ['address', 'geo', 'openingHours', 'sameAs'];
-				if (!empty(array_intersect(array_keys($value), $multiProps))) {
-					foreach ($value as $subKey => $subValue) {
-						if ($subValue !== null && $subValue !== '' && $subValue !== []) {
-							$schema[$subKey] = $subValue;
-						}
-					}
-					continue;
-				}
-			}
-
-			// Normal case: add single property
-			$schema[$fieldName] = $value;
-		}
-
-		// Return null if only @type remains
-		return (count($schema) > 1) ? $schema : null;
-	}
-
-	/**
-	 * Apply transformer to a field value
-	 */
-	private function applyTransformer(mixed $value, string $transformer, string $fieldName): mixed
-	{
-		// Check if transformer method exists in SchemaFieldHelpers
-		if (method_exists(SchemaFieldHelpers::class, $transformer)) {
-			try {
-				return SchemaFieldHelpers::$transformer($value);
-			} catch (\Throwable $e) {
-				// Log error but don't break schema output
-				error_log("Schema transformer error for {$fieldName}: {$e->getMessage()}");
-				return $value;
-			}
-		}
-
-		// No transformer found, return value as-is
-		return $value;
+		return $resolver->resolve($definition, $meta);
 	}
 
 	/**
@@ -659,12 +533,12 @@
 			$resolver = $this->getResolver();
 			$metaConfig = $this->config->meta();
 
-			if (!empty($metaConfig['title'])) {
-				$webpage['name'] = $resolver->resolve($metaConfig['title']);
+			if (!empty($metaConfig['metaTitle'])) {
+				$webpage['name'] = $resolver->resolve($metaConfig['metaTitle']);
 			}
 
-			if (!empty($metaConfig['description'])) {
-				$webpage['description'] = $resolver->resolve($metaConfig['description']);
+			if (!empty($metaConfig['metaDescription'])) {
+				$webpage['description'] = $resolver->resolve($metaConfig['metaDescription']);
 			}
 		}
 

--
Gitblit v1.10.0