'{{post_title}}', 'url' => '{{post_permalink}}', 'description' => '{{post_excerpt}}', ]; protected array $defaultSchema = [ 'type' => 'JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork', 'title' => '{{post_title}}', 'url' => '{{post_permalink}}', 'description' => '{{post_excerpt}}', ]; protected array $defaultArchive = []; public function __construct(string $slug, string $type) { if (!class_exists($type)) { error_log('Could not find schema class: '.$type); return; } $this->schema = new $type(); $this->slug = $slug; $registrar = Registrar::getInstance($this->slug); $this->cache = Cache::for('schema'); $this->referenceCache = Cache::for('schemaReference'); $this->archiveCache = Cache::for('schemaArchive'); if ($registrar) { $this->cache->connect($registrar->getType()); $this->referenceCache->connect($registrar->getType()); $this->archiveCache->connect($registrar->getType()); } $this->initFilters(); $this->registerHooks(); } public function initFilters():void { $referenceProperties = apply_filters(BASE.ucFirst($this->slug).'ReferenceProperties', $this->defaultReference); foreach ($referenceProperties as $property => $value) { $this->defineReference($property, $value); } $registrar = Registrar::getInstance($this->slug); $this->defaultArchive = [ 'type' => 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage', 'name' => $registrar->getPlural(), 'description' => $registrar->getDescription() ]; } public function registerHooks(): void { add_action('wp_head', [$this, 'outputSchema'], 1); add_filter('the_seo_framework_schema_graph_data', [$this, 'filterTSFSchema'], 10, 2); $this->maybeExcludeSingles(); } protected function maybeExcludeSingles(): void { $exclude = $this->cache->remember( 'excludeSingles', function () { $exclude = []; $registrar = Registrar::getInstance($this->slug); if ($registrar && $registrar->hasFeature('hide_single')) { $exclude = $this->excludeSingle(); } if ($registrar && $registrar->hasFeature('is_timeline')) { $exclude = array_merge($exclude, $this->excludeTimeline()); } return $exclude; } ); if (!empty($exclude)) { add_filter('the_seo_framework_sitemap_exclude_ids', $exclude); } } protected function excludeSingle():array { return get_posts([ 'post_type' => jvbCheckBase($this->slug), 'posts_per_page'=> -1, 'fields' => 'ids', 'post_status' => 'publish', ]); } protected function excludeTimeline():array { return get_posts([ 'post_type' => jvbCheckBase($this->slug), 'posts_per_page'=> -1, 'fields' => 'ids', 'post_status' => 'publish', 'post_parent__not_in' => [0], // Only get posts with a parent ]); } public function filterTSFSchema(array $graph, ?array $args):array { if (jvbTSFDoIt($this->slug, $args)){ return []; } return $graph; } public function outputSchema():void { if (is_singular()) { $this->outputSingularSchema(); } elseif (is_post_type_archive(jvbCheckBase($this->slug) || is_tax(jvbCheckBase($this->slug)))) { $this->outputArchiveSchema(); } } public function outputSingularSchema():array { $ID = get_the_ID(); $this->cache->flush(); return $this->cache->remember( $ID, function () use ($ID) { $meta = Meta::forPost($ID); $config = $this->getConfig(); $class = new $config['type'](); unset($config['type']); foreach ($config as $property => $value){ $value = Resolver::resolveForSchema($property, $value, $config, $meta); $method = 'set'.ucfirst($property); $class->$method($value); } $class->setAuthor(JVB()->seo()->getCreator(true)); $schema = $class->outputSchema(); error_log('Generated archive schema: '.print_r($schema, true)); return $schema; } ); } public function outputArchiveSchema():array { $this->archiveCache->flush(); return $this->archiveCache->remember( $this->slug, function() { $action = BASE.ucfirst($this->slug).'Archive'; $config = get_option($action, apply_filters($action, $this->defaultArchive)); if (!class_exists($config['type'])) { error_log('No class found for archive schema output: '.$config['type']); return []; } $class = new $config['type']; unset($config['type']); foreach ($config as $property=>$value) { $method = 'set'.ucfirst($property); $class->$method($value); } $class->setIsPartOf(get_home_url().'/#website'); $itemList = new render\Thing\Intangible\ItemList\ItemList(); $items = new WP_Query([ 'post_type' => jvbCheckBase($this->slug), 'posts_per_page'=> 25, 'post_status' => 'publish', 'fields' => 'ids' ]); $pos = 1; $itemListItems = []; foreach ($items->posts as $ID) { $item = $this->outputReferenceSchema($ID, false); $listItem = new render\Thing\Intangible\ListItem(); $listItem->setPosition($pos); $listItem->setItem($item); $itemListItems[] = $listItem; $pos++; } wp_reset_postdata(); $itemList->setItemListElement($itemListItems); $class->setMainEntity($itemList); $schema = $class->outputSchema(); error_log('Generated archive schema: '.print_r($schema, true)); return $schema; } ); } public function outputReferenceSchema(int $ID, bool $outputSchema = true):mixed { $this->referenceCache->flush(); $cached = $this->referenceCache->remember( $ID, function () use ($ID) { $meta = Meta::forPost($ID); $config = $this->getConfig(); $class = new $config['type'](); $class->setId(get_the_permalink($ID).'/#'.$class->getTypeName()); foreach ($this->referenceProperties as $property => $value){ $value = Resolver::resolveForSchema($property, $value, $this->schema, $meta); $method = 'set'.ucfirst($property); $class->$method($value); } $schema = $class->outputSchema(); error_log('Generated archive schema: '.print_r($schema, true)); return $class; } ); return $outputSchema ? $cached->outputSchema() : $cached; } public function getConfig(string $type = 'schema'):array { if (!in_array(strtolower($type), ['schema', 'meta', 'archive', 'reference'])) { error_log('[SEO]Schema::getConfig Invalid type: '.$type); return []; } $action = BASE.ucfirst($this->slug).ucfirst($type); $default = 'default'.ucfirst($type); return get_option($action, apply_filters($action, $this->$default)); } public function define(string $property, string $value):void { $class = $this->getConfig('schema')['type']; if (!class_exists($class)) { error_log('[SEO]Schema::defineReference Class not found: '.$class); return; } if ($property === 'type') { $this->properties[$property] = $value; return; } if (!property_exists($class, $property)){ error_log('Attempted to add non-existent property '.$property.' with value: '.print_r($value, true)); return; } $this->properties[$property] = $value; } public function defineReference(string $property, string $value):void { $class = $this->getConfig('schema')['type']; if (!class_exists($class)) { error_log('[SEO]Schema::defineReference Class not found: '.$class); return; } if ($property === 'type') { $this->referenceProperties[$property] = $value; return; } $class = new $class(); if (!property_exists($class, $property)){ error_log('Attempted to add non-existent property '.$property.' with value: '.print_r($value, true)); return; } $this->referenceProperties[$property] = $value; } public function setAllProperties(array $properties):void { foreach ($properties as $property => $value){ $this->define($property, $value); } } public function setAllReferenceProperties(array $properties):void { foreach ($properties as $property => $value){ $this->defineReference($property, $value); } } }