<?php
|
namespace JVBase\registrar\config\seo;
|
|
use JVBase\managers\Cache;
|
use JVBase\managers\SEO\render;
|
use JVBase\meta\Meta;
|
use JVBase\registrar\Registrar;
|
use WP_Query;
|
|
if (!defined('ABSPATH')) {
|
exit;
|
}
|
|
class Schema {
|
//One of the defined classes in the JVBase\managers\SEO\render namespace;
|
protected mixed $schema;
|
protected string $slug;
|
protected Meta $meta;
|
protected Cache $cache;
|
protected Cache $referenceCache;
|
protected Cache $archiveCache;
|
|
protected array $properties = [];
|
protected array $referenceProperties = [];
|
protected array $defaultReference = [
|
'name' => '{{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 = [
|
'type' => 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage',
|
'title' => '{{registrar.plural}}'
|
];
|
|
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());
|
|
switch ($registrar->getType()) {
|
case 'term':
|
$this->defaultSchema['type'] = 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage';
|
break;
|
case 'user':
|
$this->defaultSchema['type'] = 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\ProfilePage';
|
break;
|
}
|
$this->defaultArchive['description'] = '{{registrar.'.$slug.'.description}}';
|
}
|
$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);
|
add_filter('the_seo_framework_title_from_custom_field', [$this, 'filterTSFOGTitle'], 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
|
{
|
$based = jvbCheckBase($this->slug);
|
if (is_front_page() || is_singular($based) || is_post_type_archive($based) || is_tax($based)) {
|
return [];
|
}
|
|
// if (jvbTSFDoIt($this->slug, $args)){
|
// return [];
|
// }
|
return $graph;
|
}
|
|
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)))) {
|
$this->outputArchiveSchema();
|
} if ($registrar && $registrar->hasFeature('is_content') && is_single(get_option(BASE.$this->slug.'_archive'))) {
|
$this->outputContentTaxArchiveSchema();
|
}
|
}
|
public function outputSingularSchema():array
|
{
|
$ID = get_the_ID();
|
if (JVB_TESTING){
|
$this->cache->flush();
|
}
|
|
return $this->cache->remember(
|
$ID,
|
function () use ($ID) {
|
$meta = Meta::forPost($ID);
|
$config = $this->getConfig();
|
|
$class = JVB()->schemaHelper()::classFromConfig($config, $meta);
|
|
$class->setAuthor(JVB()->seo()->getCreator(true));
|
return $class->outputSchema();
|
}
|
);
|
|
}
|
|
public function outputContentTaxArchiveSchema():array
|
{
|
$ID = get_the_ID();
|
if (JVB_TESTING) {
|
$this->cache->flush();
|
}
|
return $this->cache->remember(
|
$ID,
|
function() use ($ID) {
|
$action = BASE.ucfirst($this->slug).'Schema';
|
$config = JVB()->schemaHelper()::schema($action);
|
|
if (!array_key_exists('type', $config)) {
|
$config['type'] = 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage';
|
}
|
if (!class_exists($config['type'])) {
|
error_log('No class found for archive schema output: '.$config['type']);
|
return [];
|
}
|
$class = JVB()->schemaHelper()::classFromConfig($config);
|
|
$class->setIsPartOf(get_home_url().'/#website');
|
$itemList = new render\Thing\Intangible\ItemList\ItemList();
|
$items = get_terms([
|
'taxonomy' => jvbCheckBase($this->slug),
|
// 'hide_empty' => true,
|
'fields' => 'ids',
|
]);
|
|
$pos = 1;
|
$itemListItems = [];
|
foreach ($items as $ID) {
|
$item = $this->outputReferenceSchema($ID, 'term',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);
|
|
return $class->outputSchema();
|
}
|
);
|
}
|
|
public function outputArchiveSchema():array
|
{
|
if (JVB_TESTING){
|
$this->archiveCache->flush();
|
}
|
|
return $this->archiveCache->remember(
|
$this->slug,
|
function() {
|
$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';
|
}
|
if (!class_exists($config['type'])) {
|
error_log('No class found for archive schema output: '.$config['type']);
|
return [];
|
}
|
$obj = get_queried_object();
|
$meta = (property_exists($obj, 'taxonomy')) ? Meta::forTerm($obj->term_id) : null;
|
|
$class = JVB()->schemaHelper()::classFromConfig($config, $meta);
|
|
$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, 'post',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, string $type, bool $outputSchema = true):mixed
|
{
|
if (JVB_TESTING){
|
$this->referenceCache->flush();
|
}
|
|
$cached = $this->referenceCache->remember(
|
$ID,
|
function () use ($ID, $type) {
|
switch ($type) {
|
case 'post':
|
$meta = Meta::forPost($ID);
|
break;
|
case 'term':
|
$meta = Meta::forTerm($ID);
|
break;
|
case 'user':
|
$meta = Meta::forUser($ID);
|
break;
|
default:
|
error_log('Invalid type used for reference: '.print_r($type, true));
|
$meta = null;
|
}
|
$config = $this->getConfig('archive');
|
$class = JVB()->schemaHelper()::classFromConfig($config, $meta);
|
$class->delete('about');
|
|
switch ($type) {
|
case 'post':
|
$class->setId(get_the_permalink($ID).'#'.$class->getTypeName());
|
break;
|
case 'term':
|
$class->setId(get_term_link($ID).'#'.$class->getTypeName());
|
break;
|
}
|
|
|
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 [];
|
}
|
return JVB()->schemaHelper()::getConfig($this->slug, $type);
|
}
|
|
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
|
{
|
$config = $this->getConfig();
|
if (!array_key_exists('type', $config)) {
|
$config['type'] = $this->defaultSchema['type'];
|
update_option(BASE.ucfirst($this->slug).'Schema', $config);
|
}
|
$class = $this->getConfig()['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);
|
}
|
}
|
|
public function filterTSFOGTitle(string $title, ?array $args):string{
|
$based = jvbCheckBase($this->slug);
|
|
if (is_singular($based)){
|
$config = $this->getConfig('meta');
|
$meta = Meta::forPost(get_the_ID());
|
$title = Resolver::resolve($config['name'], $meta);
|
} elseif (is_post_type_archive($based) ) {
|
$config = $this->getConfig('archive');
|
$title = $config['name'];
|
} elseif (is_tax($based)) {
|
$config = $this->getConfig('archive');
|
$meta = Meta::forTerm(get_queried_object_id());
|
$title = Resolver::resolve($config['name'], $meta);
|
}
|
return $title;
|
}
|
}
|