<?php
|
namespace JVBase\managers\SEO\render;
|
|
use JVBase\base\SchemaHelper;
|
use JVBase\inc\managers\SEO\render\Thing\Intangible\ItemList\OfferCatalog;
|
use JVBase\Instance;
|
use JVBase\managers\Cache;
|
use JVBase\managers\SEO\BreadcrumbManager;
|
use JVBase\managers\SEO\render\Thing\CreativeWork\Menu;
|
use JVBase\managers\SEO\render\Thing\CreativeWork\MenuSection;
|
use JVBase\managers\SEO\render\Thing\Intangible\MenuItem;
|
use JVBase\managers\SEO\render\Thing\Intangible\Offer;
|
use JVBase\managers\SEO\render\Thing\Intangible\Service;
|
use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness;
|
use JVBase\managers\SEO\render\Thing\Thing;
|
use JVBase\meta\Meta;
|
use JVBase\registrar\config\seo\Resolver;
|
use JVBase\registrar\Registrar;
|
use WP_Query;
|
|
if (!defined('ABSPATH')) {
|
exit;
|
}
|
class SchemaOutput {
|
use Instance;
|
protected array $types;
|
protected function __construct() {
|
add_action('wp_head', [$this, 'outputSchema'], 0);
|
$this->init();
|
}
|
|
protected function init(): void {
|
$this->types = array_map(function ($type) { return jvbCheckBase($type); }, Registrar::getRegistered());
|
}
|
public function buildSchema():array
|
{
|
$schema = [];
|
|
$registrar = false;
|
if (is_front_page()) {
|
$schema[] = $this->buildWebsiteSchema();
|
$test = $this->buildOrganizationSchema();
|
if (!empty($test)) {
|
$schema[] = $test;
|
}
|
}
|
|
if (is_singular($this->types)) {
|
$type = get_post_type();
|
$registrar = Registrar::getInstance($type);
|
|
$function = BASE.'build_singular_'.jvbNoBase($type).'_schema';
|
if (function_exists($function)) {
|
$schema = $function(get_the_ID());
|
} else if ($registrar) {
|
$seo = $registrar->getSEO();
|
$schema[] = $seo->schema()->outputSingularSchema();
|
}
|
}elseif (is_post_type_archive($this->types)) {
|
|
$type = get_queried_object();
|
$type = $type->name;
|
$registrar = Registrar::getInstance($type);
|
|
if ($registrar) {
|
$seo = $registrar->getSEO();
|
$schema[] =$seo->schema()->outputArchiveSchema();
|
}
|
} elseif (is_tax($this->types)) {
|
$type = get_queried_object();
|
$type = jvbNoBase($type->taxonomy);
|
$registrar = Registrar::getInstance($type);
|
if ($registrar ) {
|
$seo = $registrar->getSEO();
|
// error_log('SEO: '.print_r($seo->schema(), true));
|
$schema[] = $seo->schema()->outputArchiveSchema();
|
}
|
} elseif (is_post_type_archive('product')) {
|
$schema[] = $this->buildProductSchema();
|
}
|
$isContent = array_values(array_filter(array_map(function($item) {
|
return intval(get_option(BASE.$item.'_archive', false));
|
},Registrar::withFeature('is_content', 'term'))));
|
|
if (!empty($isContent) && is_page($isContent)){
|
$type = get_post_meta(get_the_id(), BASE.'for_type', true);
|
// error_log('Type: '.print_r($type, true));
|
if (!empty($type)) {
|
$registrar = Registrar::getInstance($type);
|
if ($registrar) {
|
$schema[] = $registrar->getSEO()->schema()->outputContentTaxArchiveSchema();
|
}
|
}
|
|
}
|
|
if ($registrar && !empty($registrar->getSEO()->schema()->extra())) {
|
$schema = array_merge($schema, $registrar->getSEO()->schema()->extra());
|
}
|
|
|
|
$breadcrumbs = $this->buildBreadcrumbs();
|
if ($breadcrumbs) {
|
$schema[] = $breadcrumbs;
|
}
|
|
if (!empty($schema)) {
|
if (!is_front_page()) {
|
$website = SchemaHelper::getInstance()::schema('website');
|
if (!empty($website)) {
|
|
$website = Cache::for('websiteSchema')->remember(
|
'schema',
|
function () {
|
return $this->websiteSchema();
|
}
|
);
|
array_unshift($schema, $website);
|
}
|
}
|
|
$schema = [
|
'@context' => 'https://schema.org',
|
'@graph' => $schema
|
];
|
}
|
|
return $schema;
|
}
|
|
protected function websiteSchema():array
|
{
|
$stored = SchemaHelper::getInstance()::schema('website');
|
$website = SchemaHelper::getInstance()::classFromConfig($stored);
|
|
if (!$website->getUrl() || empty($website->getUrl())){
|
$website->setUrl(get_home_url());
|
}
|
|
if (!$website->getName() || empty($website->getName())){
|
$website->setName(get_bloginfo('name'));
|
}
|
if(!$website->getCreator() || empty($website->getCreator(true))) {
|
$website->setCreator($this->getCreator(true));
|
}
|
return $website->outputSchema();
|
}
|
|
public function outputSchema():void
|
{
|
$schema = $this->buildSchema();
|
if (empty($schema)) {
|
return;
|
}
|
// $encoded = wp_json_encode($schema, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
|
// $encoded = wp_json_encode($schema, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT, 1024);
|
$encoded = wp_json_encode($schema, JSON_UNESCAPED_SLASHES, 2048);
|
if ($encoded === false) {
|
error_log('wp_json_encode failed: ' . json_last_error_msg());
|
return;
|
}
|
echo "\n<!-- SEO Schema by JakeVan -->\n";
|
echo '<script type="application/ld+json">' . "\n";
|
|
echo $encoded;
|
echo "\n" . '</script>' . "\n";
|
echo "\n" . '<!-- / SEO Schema by JakeVan -->'."\n";
|
}
|
|
public function buildBreadcrumbs():array
|
{
|
return BreadcrumbManager::getInstance()->toSchema();
|
}
|
|
public function buildWebsiteSchema(bool $full = true):array
|
{
|
if (JVB_TESTING){
|
Cache::for('websiteSchema')->flush();
|
}
|
$storedWebsite = SchemaHelper::getInstance()::schema('website');
|
|
if (!$full) {
|
return Cache::for('websiteSchema')->remember(
|
'reference',
|
function () use ($storedWebsite) {
|
$allowed = ['type','name', 'url', 'description', 'inLanguage'];
|
$storedWebsite = array_filter($storedWebsite, function ($key) use ($allowed) {
|
return in_array($key, $allowed);
|
}, ARRAY_FILTER_USE_KEY);
|
|
$website = SchemaHelper::getInstance()::classFromConfig($storedWebsite);
|
if (!$website->getName() || empty($website->getName())) {
|
$website->setName(get_bloginfo('name'));
|
}
|
if (!$website->getUrl() || empty($website->getUrl())) {
|
$website->setUrl(get_home_url());
|
}
|
if (!$website->getId() || empty($website->getId())) {
|
$website->setId(get_home_url().'/#website');
|
}
|
if (!$website->getDescription() || empty($website->getDescription())) {
|
$website->setDescription(get_bloginfo('description'));
|
}
|
if (!$website->getInLanguage() || empty($website->getInLanguage())) {
|
$website->setInLanguage('en-CA');
|
}
|
if (!$website->getPublisher() || empty($website->getPublisher())) {
|
$publisher = $this->getOptionSchemaReference('organization');
|
if ($publisher){
|
$website->setPublisher($publisher);
|
}
|
}
|
|
$website->setCreator($this->getCreator(true));
|
|
return $website->outputSchema();
|
}
|
);
|
}
|
return Cache::for('websiteSchema')->remember(
|
'full',
|
function () use ($storedWebsite) {
|
$website = SchemaHelper::getInstance()::classFromConfig($storedWebsite);
|
if (!$website->getName() || empty($website->getName())) {
|
$website->setName(get_bloginfo('name'));
|
}
|
if (!$website->getUrl() || empty($website->getUrl())) {
|
$website->setUrl(get_home_url());
|
}
|
if (!$website->getId() || empty($website->getId())) {
|
$website->setId(get_home_url().'/#website');
|
}
|
if (!$website->getDescription() || empty($website->getDescription())) {
|
$website->setDescription(get_bloginfo('description'));
|
}
|
if (!$website->getInLanguage() || empty($website->getInLanguage())) {
|
$website->setInLanguage('en-CA');
|
}
|
if (!$website->getPublisher() || empty($website->getPublisher())) {
|
$publisher = $this->getOptionSchemaReference('organization');
|
if ($publisher){
|
$website->setPublisher($publisher);
|
}
|
}
|
|
$website->setCreator($this->getCreator(true));
|
|
return $website->outputSchema();
|
}
|
);
|
}
|
|
|
public function getCreator(bool $reference = false, bool $jakeVan = false):LocalBusiness
|
{
|
if (JVB_TESTING){
|
Cache::for('JakeVanCreator')->flush();
|
}
|
|
if ($reference && $jakeVan) {
|
return Cache::for('JakeVanCreator')->remember(
|
'reference',
|
function () {
|
$creator = new LocalBusiness();
|
$creator->setId('jakevan');
|
$creator->setName('JakeVan');
|
$creator->setUrl('https://jakevan.ca/');
|
$creator->setDescription('Let\'s bring your idea to life.');
|
return $creator;
|
}
|
);
|
}
|
if ($jakeVan) {
|
return Cache::for('JakeVanCreator')->remember(
|
'full',
|
function () {
|
$creator = new LocalBusiness();
|
$creator->setName('JakeVan');
|
$creator->setId('https://jakevan.ca/#localbusiness');
|
$creator->setAlternateName(['Jake Vanderwerf', 'Rooted Romantic', 'Jacob Vanderwerf']);
|
$creator->setUrl('https://jakevan.ca');
|
$creator->setSameAs([
|
'https://bsky.app/profile/jakevan.ca',
|
]);
|
$creator->setAreaServed(['Edmonton, Alberta', 'Alberta', 'Canada']);
|
$offers = new OfferCatalog();
|
$offers->setName('Services');
|
$offers->setUrl('https://jakevan.ca/services');
|
|
$graphicDesign = new Service();
|
$graphicDesign->setName('Graphic Design');
|
$graphicDesign->setUrl('https://jakevan.ca/services/design/');
|
$graphicDesign->setDescription('From print to digital design.');
|
$websiteDesign = new Service();
|
$websiteDesign->setName('Development');
|
$websiteDesign->setUrl('https://jakevan.ca/services/development/');
|
$websiteDesign->setDescription('From basic websites to custom functionality.');
|
$strategy = new Service();
|
$strategy->setName('Strategy');
|
$strategy->setUrl('https://jakevan.ca/services/strategy/');
|
$strategy->setDescription('From developing your business plan to SEO.');
|
$art = new Service();
|
$art->setName('Art');
|
$art->setUrl('https://jakevan.ca/services/art/');
|
$art->setDescription('From unique, custom, handmade pieces to small-scale wholesale.');
|
|
$offers->setItemListElement([
|
$graphicDesign,
|
$websiteDesign,
|
$strategy,
|
$art
|
]);
|
|
|
$creator->setHasOfferCatalog($offers);
|
return $creator;
|
}
|
);
|
}
|
if ($reference) {
|
return $this->getOptionSchemaReference('organization');
|
}
|
$config = SchemaHelper::getInstance()::schema('organization');
|
|
return SchemaHelper::getInstance()::classFromConfig($config);
|
}
|
|
public function getOptionSchemaReference(string $option):mixed
|
{
|
if (!in_array(strtolower($option), array_merge(
|
['organization',
|
'website'],
|
Registrar::getRegistered()
|
))) {
|
error_log('Attempted to get schema reference for: '.$option.', but it does not exist');
|
return null;
|
}
|
|
$stored = SchemaHelper::getInstance()::reference($option);
|
|
if (empty($stored)){
|
error_log('Attempted to get schema reference for: '.$option.', but defaults not set.');
|
return null;
|
}
|
$type = $stored['type'];
|
if (!class_exists($type)){
|
error_log('Attempted to get schema reference, but class does not exist: '.$type);
|
return null;
|
}
|
$class = new $type();
|
unset($stored['type']);
|
$minimal = apply_filters(BASE.'OrganizationReference', ['name', 'url', 'sameAs', 'logo']);
|
|
foreach ($minimal as $property) {
|
$method = 'set'.ucfirst($property);
|
$value = array_key_exists($property, $stored) ? $stored[$property] : null;
|
if (!$value) {continue;}
|
|
if (str_contains($value, '{{')) {
|
$meta = null;
|
$value = Resolver::resolve($property, $value);
|
}
|
$class->$method($value);
|
}
|
return $class;
|
}
|
|
public function buildOrganizationSchema():array
|
{
|
$config = SchemaHelper::getInstance()::schema('organization');
|
|
$class = SchemaHelper::getInstance()::classFromConfig($config);
|
return ($class)? $class->outputSchema() : [];
|
}
|
|
public function buildProductSchema():array
|
{
|
$sections = aneb_menu_sections();
|
if (empty($sections)) {
|
$sections = [];
|
}
|
|
$menuSections = [];
|
|
foreach ($sections as $s) {
|
if (empty($s['id'])) {
|
continue;
|
}
|
$items = new WP_Query([
|
'post_type' => 'product',
|
'order' => 'ASC',
|
'posts_per_page' => -1,
|
'post_status' => 'publish',
|
'tax_query' => [
|
[
|
'taxonomy' => 'product_cat',
|
'terms' => (int)$s['id']
|
]
|
],
|
'fields' => 'ids',
|
]);
|
if (!$items->have_posts()) {
|
continue;
|
}
|
|
$section = new MenuSection();
|
|
$section->setName($s['name']);
|
$section->setId(get_post_type_archive_link('product').'#'.sanitize_title($s['name']));
|
if (!empty($s['description'])) {
|
$section->setDescription($s['description']);
|
}
|
|
$menuItems = [];
|
foreach ($items->posts as $ID) {
|
$meta = Meta::forPost($ID);
|
$fields = $meta->getAll();
|
if (empty($fields['variations'])) {
|
$item = $this->buildMenuItem($ID, $fields);
|
if ($item) {
|
$menuItems[] = $item;
|
}
|
} else {
|
foreach ($fields['variations'] as $variation) {
|
$item = $this->buildMenuItem($ID, $variation);
|
if ($item) {
|
$menuItems[] = $item;
|
}
|
}
|
}
|
}
|
|
if (!empty($menuItems)) {
|
$section->setHasMenuItem($menuItems);
|
$menuSections[] = $section;
|
}
|
|
|
}
|
|
if (!empty($menuSections)) {
|
$schema = new Menu();
|
$schema->setName(get_bloginfo('name') . ' | Alberta\'s Best Food Truck Menu');
|
$schema->setId(get_post_type_archive_link('product').'#menu');
|
$schema->setDescription('Southern style barbeque with Northern flair from Meeting Creek, Alberta');
|
$schema->setUrl(get_post_type_archive_link('product'));
|
|
$schema->setHasMenuSection($menuSections);
|
|
return $schema->outputSchema();
|
}
|
|
return [];
|
}
|
|
protected function buildMenuItem(int $ID, array $fields):MenuItem|false
|
{
|
$item = new MenuItem();
|
$item->setUrl(get_the_permalink($ID));
|
$base = get_post_type_archive_link('product').'#'.$ID.'-'.sanitize_title($fields['post_title']);
|
$item->setId($base);
|
$doit = false;
|
if (!empty($fields['post_thumbnail']??'')) {
|
$doit = true;
|
$item->setImage(Thing::createImageFromID($fields['post_thumbnail']));
|
}
|
|
if (!empty($fields['post_excerpt']??'')) {
|
$doit = true;
|
$item->setDescription(wp_strip_all_tags($fields['post_excerpt']));
|
}
|
|
if (!empty($fields['price']??'')) {
|
$doit = true;
|
$offer = new Offer();
|
$offer->setId($base.'-price');
|
$offer->setPrice($fields['price']);
|
$offer->setPriceCurrency('CAD');
|
$item->setOffers($offer);
|
}
|
return $doit ? $item : false;
|
}
|
}
|