From 32ce5c542c90be348895328078e392dde2052957 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Fri, 04 Sep 2026 19:15:16 +0000
Subject: [PATCH] =Working at adding defined fields to a dedicated panel in the gutenberg editor. Registrar now has AddCustomFields.php, which currently only supports post fields. Added a @wordpress/plugins base _meta/fields in /build and /src that successfully creates a panel. Next steps: determine sections and fields, group fields by section (each section to a panel), and save output
---
inc/managers/SEO/render/SchemaOutput.php | 418 +++++++++++++++++++++++++++++++++++++++++++++--------------
1 files changed, 320 insertions(+), 98 deletions(-)
diff --git a/inc/managers/SEO/render/SchemaOutput.php b/inc/managers/SEO/render/SchemaOutput.php
index 6109e90..87c2e7c 100644
--- a/inc/managers/SEO/render/SchemaOutput.php
+++ b/inc/managers/SEO/render/SchemaOutput.php
@@ -1,18 +1,30 @@
<?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\WebSite;
-use JVBase\managers\SEO\render\Thing\Intangible\OfferCatalog;
+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;
- public function __construct() {
+ protected function __construct() {
add_action('wp_head', [$this, 'outputSchema'], 0);
$this->init();
}
@@ -23,13 +35,24 @@
public function buildSchema():array
{
$schema = [];
- if (!is_front_page()) {
- $schema[] = $this->buildBasicWebsiteSchema();
+
+ $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);
- if ($registrar && !empty($registrar->getConfig('seo')['schema']??[])) {
+
+ $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();
}
@@ -39,7 +62,7 @@
$type = $type->name;
$registrar = Registrar::getInstance($type);
- if ($registrar && !empty($registrar->getConfig('seo')['schema']??[])) {
+ if ($registrar) {
$seo = $registrar->getSEO();
$schema[] =$seo->schema()->outputArchiveSchema();
}
@@ -47,23 +70,32 @@
$type = get_queried_object();
$type = jvbNoBase($type->taxonomy);
$registrar = Registrar::getInstance($type);
- if ($registrar && !empty($registrar->getConfig('seo')['schema']??[])) {
+ if ($registrar ) {
$seo = $registrar->getSEO();
- error_log('SEO: '.print_r($seo->schema(), true));
+// 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::getFeatured('is_content', 'term'))));
+ },Registrar::withFeature('is_content', 'term'))));
- if (is_page($isContent)){
+ if (!empty($isContent) && is_page($isContent)){
$type = get_post_meta(get_the_id(), BASE.'for_type', true);
- error_log('Type: '.print_r($type, true));
- $registrar = Registrar::getInstance($type);
- if ($registrar) {
- $schema[] = $registrar->getSEO()->schema()->outputContentTaxArchiveSchema();
+// 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());
}
@@ -74,41 +106,45 @@
}
if (!empty($schema)) {
- $website = get_option(BASE.'WebsiteSchema');
- if (!empty($website)) {
- if (JVB_TESTING) {
- Cache::for('websiteSchema')->flush();
+ 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);
}
- $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 = get_option(BASE.'WebsiteSchema', apply_filters(BASE.'WebsiteSchema', []));
+ $stored = SchemaHelper::getInstance()::schema('website');
+ $website = SchemaHelper::getInstance()::classFromConfig($stored);
- $seo = new WebSite();
- foreach ($stored as $property => $value) {
- $method = 'set'.ucfirst($property);
- $seo->$method($value);
+ if (!$website->getUrl() || empty($website->getUrl())){
+ $website->setUrl(get_home_url());
}
- $seo->setUrl(get_home_url());
- $seo->setName(get_bloginfo('name'));
- $seo->setCreator($this->getCreator());
- return $seo->outputSchema();
+ 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
@@ -117,9 +153,17 @@
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 wp_json_encode($schema, JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT);
+
+ echo $encoded;
echo "\n" . '</script>' . "\n";
echo "\n" . '<!-- / SEO Schema by JakeVan -->'."\n";
}
@@ -129,24 +173,75 @@
return BreadcrumbManager::getInstance()->toSchema();
}
- public function buildBasicWebsiteSchema():array
+ 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(
- 'reference',
- function () {
- $website = new WebSite();
- $website->setName(get_bloginfo('name'));
- $website->setUrl(get_home_url());
- $website->setId(get_home_url().'/#website');
- $website->setDescription(get_bloginfo('description'));
- $website->setInLanguage('en-CA');
- $publisher = $this->getOptionSchemaReference('organization');
- if ($publisher){
- $website->setPublisher($publisher);
+ '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));
@@ -157,71 +252,78 @@
}
- public function getCreator(bool $reference = false):LocalBusiness
+ public function getCreator(bool $reference = false, bool $jakeVan = false):LocalBusiness
{
if (JVB_TESTING){
Cache::for('JakeVanCreator')->flush();
}
- if ($reference) {
+ if ($reference && $jakeVan) {
return Cache::for('JakeVanCreator')->remember(
'reference',
function () {
$creator = new LocalBusiness();
+ $creator->setId('jakevan');
$creator->setName('JakeVan');
- $creator->setAlternateName('Jake Vanderwerf');
$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');
- 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.');
- $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
- ]);
+ $offers->setItemListElement([
+ $graphicDesign,
+ $websiteDesign,
+ $strategy,
+ $art
+ ]);
- $creator->setHasOfferCatalog($offers);
- return $creator;
- }
- );
+ $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
@@ -234,9 +336,9 @@
error_log('Attempted to get schema reference for: '.$option.', but it does not exist');
return null;
}
- $action = BASE.ucfirst($option).'Schema';
- $stored = get_option($action, apply_filters($action, []));
-// $stored = get_option($action, apply_filters($action, jvbDefaultSchema($option)));
+
+ $stored = SchemaHelper::getInstance()::reference($option);
+
if (empty($stored)){
error_log('Attempted to get schema reference for: '.$option.', but defaults not set.');
return null;
@@ -248,16 +350,136 @@
}
$class = new $type();
unset($stored['type']);
- $minimal = apply_filters($action.'Reference', ['name', 'url', 'sameAs', 'logo']);
+ $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;}
- $class->$method(Resolver::resolve($property, $value));
+ 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;
+ }
}
--
Gitblit v1.10.0