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\n"; echo '' . "\n"; echo "\n" . ''."\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; } }