From 266aa37c48222993bf7bdad6834e31bd08736f5e Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sat, 23 May 2026 21:22:07 +0000
Subject: [PATCH] =Post type archive meta title fix

---
 inc/registrar/config/seo/Schema.php |  280 +++++++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 268 insertions(+), 12 deletions(-)

diff --git a/inc/registrar/config/seo/Schema.php b/inc/registrar/config/seo/Schema.php
index 5144fd2..6b01794 100644
--- a/inc/registrar/config/seo/Schema.php
+++ b/inc/registrar/config/seo/Schema.php
@@ -1,6 +1,7 @@
 <?php
 namespace JVBase\registrar\config\seo;
 
+use JVBase\base\SchemaHelper;
 use JVBase\managers\Cache;
 use JVBase\managers\SEO\render;
 use JVBase\meta\Meta;
@@ -35,7 +36,7 @@
 	];
 
 	protected array $defaultArchive = [
-		'type'	=> 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage',
+		'type'	=> 'JVBase\inc\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage\CollectionPage',
 		'title' => '{{registrar.plural}}'
 	];
 
@@ -58,7 +59,7 @@
 
 			switch ($registrar->getType()) {
 				case 'term':
-					$this->defaultSchema['type'] = 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage';
+					$this->defaultSchema['type'] = 'JVBase\inc\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage\CollectionPage';
 					break;
 				case 'user':
 					$this->defaultSchema['type'] = 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\ProfilePage';
@@ -79,7 +80,7 @@
 
 		$registrar = Registrar::getInstance($this->slug);
 		$this->defaultArchive = [
-			'type'	=> 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage',
+			'type'	=> 'JVBase\inc\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage\CollectionPage',
 			'name'	=> $registrar->getPlural(),
 			'description' => $registrar->getDescription()
 		];
@@ -148,6 +149,7 @@
 	public function outputSchema():void
 	{
 		$registrar = Registrar::getInstance($this->slug);
+
 		if (is_singular()) {
 			$this->outputSingularSchema();
 		} elseif (is_post_type_archive(jvbCheckBase($this->slug) || is_tax(jvbCheckBase($this->slug)))) {
@@ -163,15 +165,25 @@
 				$this->cache->flush();
 			}
 
+			$registrar = Registrar::getInstance($this->slug);
+
 			return $this->cache->remember(
 				$ID,
-				function () use ($ID) {
+				function () use ($ID, $registrar) {
 					$meta = Meta::forPost($ID);
+					if ($registrar->hasFeature('is_faq')) {
+						return $this->outputQASchema($ID, $meta);
+					}
+					if ($registrar->hasFeature('is_timeline')) {
+						return $this->outputTimelineSchema($ID, $meta);
+					}
+
 					$config = $this->getConfig();
 
 					$class = JVB()->schemaHelper()::classFromConfig($config, $meta);
-
 					$class->setAuthor(JVB()->seo()->getCreator(true));
+
+					$class = apply_filters('jvb_single_'.$this->slug.'_schema_output', $class, $ID);
 					return $class->outputSchema();
 				}
 			);
@@ -191,7 +203,7 @@
 					$config = JVB()->schemaHelper()::schema($action);
 
 					if (!array_key_exists('type', $config)) {
-						$config['type'] = 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage';
+						$config['type'] = 'JVBase\inc\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage\CollectionPage';
 					}
 					if (!class_exists($config['type'])) {
 						error_log('No class found for archive schema output: '.$config['type']);
@@ -238,12 +250,22 @@
 					$action = BASE.ucfirst($this->slug).'Archive';
 					$config = JVB()->schemaHelper()->archive($this->slug);
 					if (!array_key_exists('type', $config)) {
-						$config['type'] = 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage';
+						$config['type'] = 'JVBase\inc\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage\CollectionPage';
 					}
 					if (!class_exists($config['type'])) {
 						error_log('No class found for archive schema output: '.$config['type']);
 						return [];
 					}
+
+					$registrar = Registrar::getInstance($this->slug);
+					if ($registrar->hasFeature('is_glossary')) {
+						return $this->outputGlossarySchema();
+					} else if ($registrar->hasFeature('is_faq')) {
+						return $this->outputFAQSchema();
+					}
+					if ($registrar->hasFeature('is_timeline')) {
+						return $this->outputTimelineArchiveSchema();
+					}
 					$obj = get_queried_object();
 					$meta = (property_exists($obj, 'taxonomy')) ? Meta::forTerm($obj->term_id) : null;
 
@@ -262,6 +284,7 @@
 					foreach ($items->posts as $ID) {
 						$item = $this->outputReferenceSchema($ID, 'post',false);
 						$listItem = new render\Thing\Intangible\ListItem();
+						$listItem->setId('listitem-'.$ID);
 						$listItem->setPosition($pos);
 						$listItem->setItem($item);
 						$itemListItems[] = $listItem;
@@ -272,7 +295,10 @@
 					$class->setMainEntity($itemList);
 
 					$schema = $class->outputSchema();
-					error_log('Generated archive schema: '.print_r($schema, true));
+					if (JVB_TESTING) {
+//						error_log('Generated archive schema: '.print_r($schema, true));
+					}
+
 					return $schema;
 				}
 			);
@@ -301,7 +327,7 @@
 						error_log('Invalid type used for reference: '.print_r($type, true));
 						$meta = null;
 				}
-				$config = $this->getConfig('archive');
+				$config = $this->getConfig();
 				$class = JVB()->schemaHelper()::classFromConfig($config, $meta);
 				$class->delete('about');
 
@@ -331,6 +357,28 @@
 		return JVB()->schemaHelper()::getConfig($this->slug, $type);
 	}
 
+	public function resolveMeta(array $config, Meta $meta):array
+	{
+		foreach ($config as $property => $value) {
+			if (is_array($value)) {
+				$config[$property] = $this->resolveMeta($value, $meta);
+				if ($property === 'additionalProperty' && (!array_key_exists('value', $config[$property]) || empty($config[$property]['value']))) {
+					unset($config[$property]);
+				}
+			}
+			if (is_string($value) && str_contains($value, '{{')) {
+				$value = Resolver::resolve($value, $meta);
+				if (empty($value)){
+					unset($config[$property]);
+				} else {
+					$config[$property] = $value;
+				}
+			}
+		}
+
+		return $config;
+	}
+
 	public function define(string $property, string $value):void
 	{
 		$class = $this->getConfig('schema')['type'];
@@ -355,7 +403,7 @@
 			$config['type'] = $this->defaultSchema['type'];
 			update_option(BASE.ucfirst($this->slug).'Schema', $config);
 		}
-		$class = $this->getConfig()['type'];
+		$class = $config['type'];
 		if (!class_exists($class)) {
 			error_log('[SEO]Schema::defineReference Class not found: '.$class);
 			return;
@@ -392,15 +440,223 @@
 		if (is_singular($based)){
 			$config = $this->getConfig('meta');
 			$meta = Meta::forPost(get_the_ID());
-			$title = Resolver::resolve($config['name'], $meta);
+			$title = Resolver::resolve($config['name']??$config['title']??'', $meta);
 		} elseif (is_post_type_archive($based) ) {
 			$config = $this->getConfig('archive');
-			$title = $config['name'];
+			$title = Resolver::resolve($config['name'], null);
 		} elseif (is_tax($based)) {
 			$config = $this->getConfig('archive');
 			$meta = Meta::forTerm(get_queried_object_id());
 			$title = Resolver::resolve($config['name'], $meta);
+		} else {
+			error_log('[Schema]::filterTSFOGTitle Unmatched condition: '.$this->slug);
 		}
 		return $title;
 	}
+	public function outputQASchema(int $ID, Meta $meta):array
+	{
+		$registrar = Registrar::getInstance($this->slug);
+		global $wp;
+		$current = get_home_url(null, $wp->request).'/';
+		$config = $this->getConfig();
+
+		$config['type']	= 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\QAPage';
+		$page = SchemaHelper::classFromConfig($config, $meta);
+
+		$post = get_queried_object();
+		$question = [
+			'id'	=> $current.'#question-'.$post->post_name,
+			'type' => 'JVBase\inc\managers\SEO\render\Thing\CreativeWork\Comment\Question',
+			'name'	=> $meta->get('post_title'),
+			'acceptedAnswer' => [
+				'id'	=> $current.'#answer-'.$post->post_name,
+				'type'	=> 'JVBase\inc\managers\SEO\render\Thing\CreativeWork\Comment\Answer',
+				'text'	=> wp_strip_all_tags(str_replace("\n", '', $meta->get('post_content'))),
+			],
+		];
+		$question = SchemaHelper::classFromConfig($question);
+		$page->setMainEntity($question);
+		$page->setAuthor(JVB()->seo()->getCreator(true));
+		return $page->outputSchema();
+
+	}
+	public function outputTimelineSchema(int $ID, Meta $meta):array
+	{
+		$registrar = Registrar::getInstance($this->slug);
+		global $wp;
+		$current = get_home_url(null, $wp->request).'/';
+		$config = $this->getConfig();
+
+		$config['type']	= 'JVBase\inc\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage\ImageGallery';
+		$this->resolveMeta($config, $meta);
+		$page = SchemaHelper::classFromConfig($config, $meta);
+
+		$post = get_queried_object();
+		$images = [];
+		$children = new WP_Query([
+			'post_type'=> jvbCheckBase($this->slug),
+			'post_status'	=> 'publish',
+			'post_parent'	=> $post->ID,
+		]);
+		$children = $children->posts;
+		array_unshift($children, $post);
+
+		foreach ($children as $index => $child) {
+			$meta = Meta::forPost($child->ID);
+
+			$image = render\Thing\Thing::createImageFromID($meta->get('post_thumbnail'));
+			$image->setId(($index === 0) ? '#before-treatment' : '#treatment-'.$index);
+			$image->setPosition($index+1);
+			$image->setName(($index === 0) ? 'Before Laser Tattoo Removal' : 'After '.$index.' Laser Tattoo Removal Treatments');
+			if (!empty ($meta->get('post_excerpt'))){
+				$image->setDescription($meta->get('post_excerpt'));
+			}
+
+			$images[] = $image;
+		}
+
+		$page->setImage($images);
+		$page->setAuthor(JVB()->seo()->getCreator(true));
+		return $page->outputSchema();
+
+	}
+
+	/*********************************************
+	 * Archive Presets
+	*********************************************/
+	public function outputFAQSchema():array
+	{
+		$registrar = Registrar::getInstance($this->slug);
+		global $wp;
+		$current = get_home_url(null, $wp->request).'/';
+		$config = $this->getConfig('archive');
+		$page = [
+			'id'	=> $current.'#'.$registrar->getSlug(),
+			'type'	=> 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\FAQPage',
+			'name'	=> array_key_exists('name', $config) ? $config['name'] : $registrar->getPlural(),
+			'description' => array_key_exists('description', $config) ? $config['description'] : $registrar->getDescription(),
+			'url'	=> $current,
+		];
+
+		$page = SchemaHelper::classFromConfig($page);
+
+		$args = [
+			'post_type'		=> $registrar->getBased(),
+			'posts_per_page'=> -1,
+			'post_status' 	=> 'publish',
+		];
+		$obj = get_queried_object();
+
+		if (property_exists($obj, 'taxonomy')) {
+			$page->setName('FAQ on '.$obj->name);
+
+			$args['post_type'] = array_map('jvbCheckBase', $registrar->registrar->for);
+
+			$args['tax_query'] = [
+				[
+					'taxonomy'	=> $obj->taxonomy,
+					'terms'		=> $obj->term_id,
+				]
+			];
+		}
+
+		$questions = [];
+		$posts = new WP_Query($args);
+		foreach ($posts->posts as $post) {
+			$meta = Meta::forPost($post->ID);
+			$question = [
+				'id'	=> $current.'#question-'.$post->post_name,
+				'type' => 'JVBase\inc\managers\SEO\render\Thing\CreativeWork\Comment\Question',
+				'name'	=> $meta->get('post_title'),
+				'acceptedAnswer' => [
+					'id'	=> $current.'#answer-'.$post->post_name,
+					'type'	=> 'JVBase\inc\managers\SEO\render\Thing\CreativeWork\Comment\Answer',
+					'text'	=> $meta->get('post_excerpt'),
+				],
+				'url'	=> get_the_permalink($post->ID),
+			];
+			$questions[] = SchemaHelper::classFromConfig($question);
+		}
+		wp_reset_postdata();
+		$page->setMainEntity($questions);
+
+		return $page->outputSchema();
+
+	}
+	public function outputTimelineArchiveSchema():array
+	{
+		$registrar = Registrar::getInstance($this->slug);
+		global $wp;
+		$current = get_home_url(null, $wp->request).'/';
+		$config = $this->getConfig('archive');
+		$page = array_merge($config, [
+			'id'	=> $current.'#'.$registrar->getSlug(),
+			'type'	=> 'JVBase\inc\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage\CollectionPage',
+			'name'	=> array_key_exists('name', $config) ? $config['name'] : $registrar->getPlural(),
+			'description' => array_key_exists('description', $config) ? $config['description'] : $registrar->getDescription(),
+			'url'	=> $current,
+		]);
+		$page = SchemaHelper::classFromConfig($page);
+
+		$parts = [];
+		$timelines = new WP_Query([
+			'post_type'		=> $registrar->getBased(),
+			'posts_per_page'=> 50,
+			'post_status' 	=> 'publish',
+			'post_parent'	=> 0,
+		]);
+		foreach ($timelines->posts as $post) {
+			$item = $this->outputReferenceSchema($post->ID, 'post', false);
+			$item->setId($current.'#'.$post->post_name);
+			$item->setName($post->post_title);
+			$item->setUrl(get_the_permalink($post->ID));
+			$parts[] = $item;
+		}
+
+		$page->setHasPart($parts);
+		return $page->outputSchema();
+	}
+	public function outputGlossarySchema():array
+	{
+		$registrar = Registrar::getInstance($this->slug);
+		global $wp;
+		$current = get_home_url(null, $wp->request).'/';
+		$config = $this->getConfig('archive');
+		$page = [
+			'id'	=> $current.'#'.$registrar->getSlug(),
+			'type'	=> 'JVBase\inc\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage\CollectionPage',
+			'name'	=> array_key_exists('name', $config) ? $config['name'] : $registrar->getPlural(),
+			'description' => array_key_exists('description', $config) ? $config['description'] : $registrar->getDescription(),
+			'url'	=> $current,
+		];
+		$page = SchemaHelper::classFromConfig($page);
+
+
+		//Defined Termset
+		$termset = [
+			'type'	=> 'JVBase\managers\SEO\render\Thing\CreativeWork\DefinedTermSet',
+			'id'	=> $current.'#definedtermset',
+			'name'	=> $registrar->getPlural(),
+			'description' => $registrar->getDescription(),
+		];
+		$termset = SchemaHelper::classFromConfig($termset);
+
+		$terms = new WP_Query([
+			'post_type'	=> $registrar->getBased(),
+			'posts_per_page' => -1,
+			'post_status' => 'publish',
+		]);
+
+		$outputTerms = [];
+		foreach ($terms->posts as $post) {
+			$item = $this->outputReferenceSchema($post->ID, 'post', false);
+			$item->setId($current.'#'.$post->post_name);
+			$item->setName($post->post_title);
+			$outputTerms[] = $item;
+		}
+		$termset->setHasDefinedTerm($outputTerms);
+
+		$page->setMainEntity($termset);
+		return $page->outputSchema();
+	}
 }

--
Gitblit v1.10.0