<?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 = [];
|
|
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);
|
}
|
}
|
}
|