From 9bbeea742424837fb58207d88e10dbca0b2cae04 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sun, 03 May 2026 22:04:17 +0000
Subject: [PATCH] =SEO Field registration and formatting
---
inc/managers/SEO/render/Traits/_Properties/sloganTrait.php | 13
inc/managers/SEO/render/Traits/_Properties/openingHoursSpecificationTrait.php | 68 +++
inc/managers/SEO/render/Traits/_Properties/keywordsTrait.php | 27 +
inc/managers/SEO/render/Traits/_Properties/offersTrait.php | 171 ++++++++-
inc/managers/SEO/render/Traits/_Properties/alternateNameTrait.php | 18 +
inc/managers/SEO/render/Traits/_Properties/additionalPropertyTrait.php | 6
inc/managers/SEO/render/Traits/_Properties/dissolutionDateTrait.php | 35 +
inc/managers/SEO/render/Traits/_Properties/awardTrait.php | 57 ++
inc/managers/SEO/render/Traits/_Properties/hasOfferCatalogTrait.php | 103 +++++
inc/managers/SEO/render/Traits/_Properties/photoTrait.php | 24 +
inc/registrar/Fields.php | 190 +++------
inc/managers/SEO/render/Traits/_Properties/amenityFeatureTrait.php | 63 +++
inc/managers/SEO/render/Traits/_Properties/paymentAcceptedTrait.php | 33 +
inc/managers/SEO/render/Traits/_Properties/aggregateRatingTrait.php | 114 ++++-
inc/managers/SEO/render/Traits/_Properties/reviewTrait.php | 156 ++++++++
15 files changed, 876 insertions(+), 202 deletions(-)
diff --git a/inc/managers/SEO/render/Traits/_Properties/additionalPropertyTrait.php b/inc/managers/SEO/render/Traits/_Properties/additionalPropertyTrait.php
index ab417a8..191efbe 100644
--- a/inc/managers/SEO/render/Traits/_Properties/additionalPropertyTrait.php
+++ b/inc/managers/SEO/render/Traits/_Properties/additionalPropertyTrait.php
@@ -38,6 +38,12 @@
return property_exists($property, 'value') && !empty($property->value);
});
}
+ if (!empty($this->additionalProperty)) {
+ if (!is_array($this->additionalProperty)) {
+ $this->additionalProperty = [$this->additionalProperty];
+ }
+ $additionalProperty = array_merge($this->additionalProperty, $additionalProperty);
+ }
$this->additionalProperty = $additionalProperty;
}
diff --git a/inc/managers/SEO/render/Traits/_Properties/aggregateRatingTrait.php b/inc/managers/SEO/render/Traits/_Properties/aggregateRatingTrait.php
index 314b685..2b67d27 100644
--- a/inc/managers/SEO/render/Traits/_Properties/aggregateRatingTrait.php
+++ b/inc/managers/SEO/render/Traits/_Properties/aggregateRatingTrait.php
@@ -3,6 +3,8 @@
use JVBase\base\SchemaHelper;
use JVBase\managers\SEO\render\Thing\Intangible\Rating\AggregateRating;
+use JVBase\meta\Meta;
+use JVBase\registrar\Fields;
if (!defined('ABSPATH')) {
exit;
@@ -29,32 +31,94 @@
$this->aggregateRating = $aggregateRating;
}
- public function getAggregateRatingFieldConfig():array
+ public function setAggregateRatingField(Fields $fields):void
{
- return [
- 'type' => 'group',
- 'label' => 'Aggregate/Average Rating',
- 'wrap' => 'details',
- 'fields' => [
- 'ratingCount' => [
- 'type' => 'number',
- 'label' => 'The total number of ratings (without text)',
- ],
- 'reviewCount' => [
- 'type' => 'number',
- 'label' => 'The total number of reviews (with text)'
- ],
- 'bestRating' => [
- 'type' => 'number',
- 'label' => 'The best rating',
- 'default'=> 5,
- ],
- 'worstRating' => [
- 'type' => 'number',
- 'label' => 'The worst rating',
- 'default'=> 1,
- ]
+ $fields->addField('average_rating', [
+ 'type' => 'select',
+ 'label' => __('Average Rating', 'jvb'),
+ 'options' => [
+ 'none' => 'None',
+ '0.5' => '0.5',
+ '1' => '1',
+ '1.5' => '1.5',
+ '2' => '2',
+ '2.5' => '2.5',
+ '3' => '3',
+ '3.5' => '3.5',
+ '4' => '4',
+ '4.5' => '4.5',
+ '5' => '5',
+ ],
+ 'default' => 'none',
+ ]);
+ $fields->addField('ratingCount', [
+ 'type' => 'number',
+ 'label' => __('The total number of ratings', 'jvb'),
+ 'condition' => [
+ 'field' => 'average_rating',
+ 'operator' => '!=',
+ 'value' => 'none',
]
- ];
+ ]);
+
+ $fields->addField('reviewCount', [
+ 'type' => 'number',
+ 'label' => __('The total number of reviews (with text)', 'jvb'),
+ 'condition' => [
+ 'field' => 'average_rating',
+ 'operator' => '!=',
+ 'value' => 'none',
+ ]
+ ]);
+
+ $fields->addField('bestRating', [
+ 'type' => 'number',
+ 'label' => __('The best possible rating value (top of scale)', 'jvb'),
+ 'default' => 5,
+ 'condition' => [
+ 'field' => 'average_rating',
+ 'operator' => '!=',
+ 'value' => 'none',
+ ]
+ ]);
+ $fields->addField('worstRating', [
+ 'type' => 'number',
+ 'label' => __('The worst possible rating value (bottom of scale)', 'jvb'),
+ 'default' => 1,
+ 'condition' => [
+ 'field' => 'average_rating',
+ 'operator' => '!=',
+ 'value' => 'none',
+ ]
+ ]);
+ }
+
+ public function formatAggregateRatingField(Meta $meta):void
+ {
+ [$average, $ratingCount, $reviewCount, $bestRating, $worstRating] = $meta->getAll([
+ 'average_rating',
+ 'ratingCount',
+ 'reviewCount',
+ 'bestRating',
+ 'worstRating'
+ ]);
+
+ if (!empty($average)) {
+ $rating = new AggregateRating();
+ $rating->setRatingValue((float)$average);
+ if (!empty($ratingCount)) {
+ $rating->setRatingCount($ratingCount);
+ }
+ if (!empty($reviewCount)){
+ $rating->setReviewCount($reviewCount);
+ }
+ if ($bestRating !== 5) {
+ $rating->setBestRating($bestRating);
+ }
+ if ($worstRating !== 1) {
+ $rating->setWorstRating($worstRating);
+ }
+ $this->setAggregateRating($rating);
+ }
}
}
diff --git a/inc/managers/SEO/render/Traits/_Properties/alternateNameTrait.php b/inc/managers/SEO/render/Traits/_Properties/alternateNameTrait.php
index 8eb38e5..f637660 100644
--- a/inc/managers/SEO/render/Traits/_Properties/alternateNameTrait.php
+++ b/inc/managers/SEO/render/Traits/_Properties/alternateNameTrait.php
@@ -2,6 +2,7 @@
namespace JVBase\managers\SEO\render\Traits\_Properties;
use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+use JVBase\registrar\Fields;
if (!defined('ABSPATH')) {
exit;
@@ -27,4 +28,21 @@
}
$this->alternateName = $alternateName;
}
+
+ public function setAlternateNameField(Fields $fields):void
+ {
+ $fields->addField(
+ 'alternate_name',
+ [
+ 'type' => 'repeater',
+ 'label' => 'Alternate Name',
+ 'fields' => [
+ 'name' => [
+ 'type' => 'text',
+ 'label' => 'Name',
+ ]
+ ]
+ ]
+ );
+ }
}
diff --git a/inc/managers/SEO/render/Traits/_Properties/amenityFeatureTrait.php b/inc/managers/SEO/render/Traits/_Properties/amenityFeatureTrait.php
index 3a72e23..d188897 100644
--- a/inc/managers/SEO/render/Traits/_Properties/amenityFeatureTrait.php
+++ b/inc/managers/SEO/render/Traits/_Properties/amenityFeatureTrait.php
@@ -2,7 +2,10 @@
namespace JVBase\managers\SEO\render\Traits\_Properties;
use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\LocationFeatureSpecification;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PropertyValue;
use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+use JVBase\meta\Meta;
+use JVBase\registrar\Fields;
if (!defined('ABSPATH')) {
exit;
@@ -26,15 +29,61 @@
$this->amenityFeature = $amenityFeature;
}
- public function getAmenityFeatureFieldConfig():array
+ public function setAmenityFeatureField(Fields $fields):void
{
- return [
- 'type' => 'repeater',
- 'label' => 'Amenity Feature',
- 'hint' => 'An amenity feature (e.g. a characteristic or service) of the Accommodation. This generic property does not make a statement about whether the feature is included in an offer for the main accommodation or available at extra costs.',
- 'fields' => [
-
+ $fields->addField('amenityFeature', [
+ 'type' => 'set',
+ 'label' => __('Amenities', 'jvb'),
+ 'options' => [
+ 'Wheelchair Accessible' => 'Wheelchair Accessible',
+ 'Free Parking' => 'Free Parking',
+ 'Private Rooms' => 'Private Rooms',
+ 'Air Conditioning' => 'Air Conditioning',
+ 'WiFi' => 'WiFi',
+ 'Gender Neutral Restroom' => 'Gender Neutral Restroom',
+ 'LGBTQ+ Friendly' => 'LGBTQ+ Friendly',
+ 'Sterilization Room' => 'Sterilization Room',
+ 'Refreshments Available' => 'Refreshments Available',
+ 'Street Level Access' => 'Street Level Access',
+ 'Single Use Needles' => 'Single Use Needles',
+ 'Consultation Room' => 'Consultation Room',
+ 'Aftercare Products Available' => 'Aftercare Products Available',
+ 'Walk-Ins Welcome' => 'Walk-Ins Welcome',
]
+ ]);
+ }
+
+ public function formatAmenityFeatureField(Meta $meta):void
+ {
+ $amenities = $meta->get('amenityFeature');
+
+ $properties = [
+ 'Walk-Ins Welcome',
+ 'LGBTQ+ Friendly',
];
+
+ if (!empty($amenities)) {
+ $out = [];
+ $prop = [];
+ foreach ($amenities as $amenity) {
+ if (in_array($amenity, $properties)) {
+ $pr = new PropertyValue();
+ $pr->setName($amenity);
+ $pr->setValue(true);
+ $prop[] = $pr;
+ } else {
+ $am = new LocationFeatureSpecification();
+ $am->setName($amenity);
+ $am->setValue(true);
+ $out[] = $am;
+ }
+ }
+ if (!empty($out)) {
+ $this->setAmenityFeature($out);
+ }
+ if (!empty($prop)) {
+ $this->setAdditionalProperty($prop);
+ }
+ }
}
}
diff --git a/inc/managers/SEO/render/Traits/_Properties/awardTrait.php b/inc/managers/SEO/render/Traits/_Properties/awardTrait.php
index b0a678a..94672c2 100644
--- a/inc/managers/SEO/render/Traits/_Properties/awardTrait.php
+++ b/inc/managers/SEO/render/Traits/_Properties/awardTrait.php
@@ -2,6 +2,8 @@
namespace JVBase\managers\SEO\render\Traits\_Properties;
use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+use JVBase\meta\Meta;
+use JVBase\registrar\Fields;
if (!defined('ABSPATH')) {
exit;
@@ -24,19 +26,56 @@
}
$this->award = $award;
}
- public function getAwardFieldConfig():array
+
+ public function setAwardField(Fields $fields):void
{
- return [
- 'type' => 'repeater',
- 'label' => 'Award(s)',
- 'hint' => 'List any awards won by this Thing.',
+ $fields->addField('awards', [
+ 'type' => 'tagList',
+ 'label' => __('Award(s)', 'jvb'),
+ 'hint' => 'List any awards won by this Thing.',
'fields' => [
- 'award' => [
+ 'name' => [
'type' => 'text',
- 'label' => 'Award',
- 'hint' => 'Include the award name, year, and presenter (if any)'
+ 'label' => __('Award Name', 'jvb'),
+ 'required' => true,
+ ],
+ 'presenter' => [
+ 'type' => 'text',
+ 'label' => __('Presenter', 'jvb'),
+ ],
+ 'year' => [
+ 'type' => 'number',
+ 'label' => __('Year', 'jvb')
]
]
- ];
+ ]);
+ }
+
+ public function formatAwardField(Meta $meta):void
+ {
+ $awards = $meta->get('awards');
+ $out = [];
+ if (!empty($awards)) {
+ foreach ($awards as $a) {
+ $award = false;
+ if (!empty($a['name'])) {
+ $award = '"'.$a['name'].'"';
+ }else {
+ continue;
+ }
+ if (!empty($a['presenter'])) {
+ $award .= ', presented by '.$a['presenter'];
+ }
+ if (!empty($a['year'])) {
+ $award .= ' - '.$a['year'];
+ }
+ if ($award) {
+ $out[] = $award;
+ }
+ }
+ }
+ if (!empty ($out)) {
+ $this->setAward($out);
+ }
}
}
diff --git a/inc/managers/SEO/render/Traits/_Properties/dissolutionDateTrait.php b/inc/managers/SEO/render/Traits/_Properties/dissolutionDateTrait.php
index 9b51d29..c327327 100644
--- a/inc/managers/SEO/render/Traits/_Properties/dissolutionDateTrait.php
+++ b/inc/managers/SEO/render/Traits/_Properties/dissolutionDateTrait.php
@@ -2,6 +2,8 @@
namespace JVBase\managers\SEO\render\Traits\_Properties;
use JVBase\managers\SEO\render\DataType\Date;
+use JVBase\meta\Meta;
+use JVBase\registrar\Fields;
if (!defined('ABSPATH')) {
exit;
@@ -31,4 +33,37 @@
'hint' => 'IMPORTANT: Do not fill this out, unless your business has actually closed.'
];
}
+ public function setDissolutionDateField(Fields $fields):void
+ {
+ $fields->addField(
+ 'permanently_close',
+ [
+ 'type' => 'true_false',
+ 'label' => __('Permanently Close', 'jvb'),
+ 'hint' => '*IMPORTANT* This signals to search engines that this business is no longer in business. Use only if your shop is closing!',
+ ]
+ );
+ $fields->addField(
+ 'dissolution_date',
+ [
+ 'type' => 'date',
+ 'label' => __('Dissolution Date', 'jvb'),
+ 'condition' => [
+ 'field' => 'permanently_close',
+ 'operator' => '==',
+ 'value' => true,
+ ]
+ ]
+ );
+ }
+
+ public function formatDissolutionDateField(Meta $meta):void
+ {
+ [$closed, $dissolution] = $meta->getAll(['permanently_close', 'dissolution_date']);
+ if (!empty($closed) && $closed === true) {
+ if (!empty($dissolution)) {
+ $this->setDissolutionDate($dissolution);
+ }
+ }
+ }
}
diff --git a/inc/managers/SEO/render/Traits/_Properties/hasOfferCatalogTrait.php b/inc/managers/SEO/render/Traits/_Properties/hasOfferCatalogTrait.php
index 0230b99..e7d32a7 100644
--- a/inc/managers/SEO/render/Traits/_Properties/hasOfferCatalogTrait.php
+++ b/inc/managers/SEO/render/Traits/_Properties/hasOfferCatalogTrait.php
@@ -3,6 +3,12 @@
use JVBase\base\SchemaHelper;
use JVBase\inc\managers\SEO\render\Thing\Intangible\ItemList\OfferCatalog;
+use JVBase\managers\SEO\render\Thing\Intangible\Offer;
+use JVBase\managers\SEO\render\Thing\Intangible\Service;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PriceSpecification;
+use JVBase\managers\SEO\render\Thing\Product\Product;
+use JVBase\meta\Meta;
+use JVBase\registrar\Fields;
use JVBase\registrar\Registrar;
if (!defined('ABSPATH')) {
@@ -97,4 +103,101 @@
]
];
}
+
+ public function setHasOfferCatalogField(Fields $fields):void
+ {
+ $fields->addField(
+ 'hasOfferCatalog',
+ [
+ 'type' => 'repeater',
+ 'label' => 'Main Products & Services',
+ 'fields' => [
+ 'type' => [
+ 'type' => 'radio',
+ 'label' => __('Select Type', 'jvb'),
+ 'options' => [
+ 'product' => 'Product',
+ 'service' => 'Service'
+ ],
+ 'required' => true,
+ ],
+ 'name' => [
+ 'type' => 'text',
+ 'label' => __('Name of Product or Service', 'jvb'),
+ 'required' => true,
+ ],
+ 'description' => [
+ 'type' => 'textarea',
+ 'label' => __('Description (optional)', 'jvb'),
+ ],
+ 'price' => [
+ 'type' => 'text',
+ 'subtype' => 'number',
+ 'label' => __('Price', 'jvb'),
+ ],
+ 'unitText' => [
+ 'type' => 'radio',
+ 'label' => 'Price per unit',
+ 'options' => [
+ 'hour' => 'Hour',
+ 'unit' => 'Unit',
+ ],
+ 'default' => 'unit',
+ ]
+ ],
+ ]
+ );
+ }
+
+ public function formatHasOfferCatalogField(Meta $meta):void
+ {
+ $catalog = $meta->get('hasOfferCatalog');
+ if (!empty($catalog)) {
+ $offerCatalog = [];
+ $name = '';
+ $services = array_filter($catalog, function ($item) {
+ return $item['type'] === 'service';
+ });
+ $products =array_filter($catalog, function ($item) {
+ return $item['type'] === 'product';
+ });
+ if (count($products) > 0) {
+ $name = 'Products';
+ if (count($services) > 0) {
+ $name .= ' & ';
+ }
+ }
+ if (count($services) > 0) {
+ $name .= 'Services';
+ }
+ foreach ($catalog as $row) {
+ $offer = new Offer();
+
+ $item = match($row['type']) {
+ 'product' => new Product(),
+ 'service' => new Service(),
+ };
+
+ $item->setName($row['name']);
+ if (!empty($row['description'])) {
+ $item->setDescription($row['description']);
+ }
+ if (!empty($row['price'])) {
+ $price = new PriceSpecification();
+ $price->setPrice($row['price']);
+ $price->setPriceCurrency('CAD');
+ $price->setUnitText($row['unitText']);
+ $offer->setPriceSpecification($price);
+ }
+ $offer->setItemOffered($item);
+ $offerCatalog[] = $offer;
+ }
+ if (!empty(!$offerCatalog)) {
+ $final = new OfferCatalog();
+ $final->setName($name);
+ $final->setItemListElement($offerCatalog);
+ $this->sethasOfferCatalog($final);
+ }
+ }
+ }
}
diff --git a/inc/managers/SEO/render/Traits/_Properties/keywordsTrait.php b/inc/managers/SEO/render/Traits/_Properties/keywordsTrait.php
index 658ffff..5ddbb33 100644
--- a/inc/managers/SEO/render/Traits/_Properties/keywordsTrait.php
+++ b/inc/managers/SEO/render/Traits/_Properties/keywordsTrait.php
@@ -3,6 +3,8 @@
use JVBase\managers\SEO\render\Thing\Intangible\DefinedTerm;
use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+use JVBase\meta\Meta;
+use JVBase\registrar\Fields;
if (!defined('ABSPATH')) {
exit;
@@ -25,4 +27,29 @@
}
$this->keywords = $keywords;
}
+
+ public function setKeywordsField(Fields $fields):void
+ {
+ $fields->addField(
+ 'keywords',
+ [
+ 'type' => 'repeater',
+ 'label' => 'Keywords',
+ 'fields' => [
+ 'keyword' => [
+ 'type' => 'text',
+ 'label' => 'Keyword',
+ ],
+ ],
+ ]
+ );
+ }
+
+ public function formatKeywordsField(Meta $meta):void
+ {
+ $keywords = $meta->get('keywords');
+ if (!empty($keywords)) {
+ $this->setKeywords($keywords);
+ }
+ }
}
diff --git a/inc/managers/SEO/render/Traits/_Properties/offersTrait.php b/inc/managers/SEO/render/Traits/_Properties/offersTrait.php
index c1dda00..a97162e 100644
--- a/inc/managers/SEO/render/Traits/_Properties/offersTrait.php
+++ b/inc/managers/SEO/render/Traits/_Properties/offersTrait.php
@@ -1,9 +1,17 @@
<?php
namespace JVBase\managers\SEO\render\Traits\_Properties;
+use JVBase\inc\managers\SEO\render\Thing\Intangible\ItemList\OfferCatalog;
use JVBase\managers\SEO\render\Thing\Intangible\Demand;
use JVBase\managers\SEO\render\Thing\Intangible\Offer;
+use JVBase\managers\SEO\render\Thing\Intangible\Service;
+use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\PriceSpecification;
+use JVBase\managers\SEO\render\Thing\Product\Product;
use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+use JVBase\meta\Meta;
+use JVBase\registrar\Fields;
+use JVBase\registrar\Registrar;
+use WP_Query;
if (!defined('ABSPATH')) {
exit;
@@ -30,34 +38,155 @@
}
$this->offers = $offers;
}
- public function getOpeningHoursFieldConfig():array
+ public function setOffersField(Fields $fields):void
{
- return [
+ $fields->addField('generate_offers_from_content',
+ [
+ 'type' => 'true_false',
+ 'label' => __('Generate Offers from Content', 'jvb'),
+ ]);
+ $fields->addField('seo_offers', [
'type' => 'repeater',
- 'label' => 'Opening Hours',
+ 'label' => __('Offers', 'jvb'),
+ 'condition' => [
+ 'field' => 'generate_offers_from_content',
+ 'value' => true,
+ 'operator' => '=='
+ ],
'fields' => [
- 'dayOfWeek' => [
+ 'type' => [
'type' => 'radio',
- 'label' => 'Day(s) of Week',
+ 'label' => __('Select Type', 'jvb'),
'options' => [
- 'Mo' => 'Monday',
- 'Tu' => 'Tuesday',
- 'We' => 'Wednesday',
- 'Th' => 'Thursday',
- 'Fr' => 'Friday',
- 'Sa' => 'Saturday',
- 'Su' => 'Sunday'
- ]
+ 'product' => 'Product',
+ 'service' => 'Service'
+ ],
+ 'required' => true,
],
- 'opens' => [
- 'type' => 'time',
- 'label' => 'Opens At',
+ 'name' => [
+ 'type' => 'text',
+ 'label' => __('Name of Product or Service', 'jvb'),
+ 'required' => true,
],
- 'closes' => [
- 'type' => 'time',
- 'label' => 'Closes At',
+ 'description' => [
+ 'type' => 'textarea',
+ 'label' => __('Description (optional)', 'jvb'),
+ ],
+ 'price' => [
+ 'type' => 'text',
+ 'subtype' => 'number',
+ 'label' => __('Price', 'jvb'),
+ ],
+ 'unitText' => [
+ 'type' => 'radio',
+ 'label' => 'Price per unit',
+ 'options' => [
+ 'hour' => 'Hour',
+ 'unit' => 'Unit',
+ ],
+ 'default' => 'unit',
]
- ]
- ];
+ ],
+ ]);
+ }
+
+ public function formatOffersField(Meta $meta):void
+ {
+ $generate = $meta->get('generate_offers_from_content');
+ if (!empty($generate) && $generate === true) {
+ $this->generateOffersFromContent($meta);
+ return;
+ }
+ $catalog = $meta->get('seo_offers');
+ if (!empty($catalog)) {
+ $offerCatalog = [];
+ $name = '';
+ $services = array_filter($catalog, function ($item) {
+ return $item['type'] === 'service';
+ });
+ $products =array_filter($catalog, function ($item) {
+ return $item['type'] === 'product';
+ });
+ if (count($products) > 0) {
+ $name = 'Products';
+ if (count($services) > 0) {
+ $name .= ' & ';
+ }
+ }
+ if (count($services) > 0) {
+ $name .= 'Services';
+ }
+ foreach ($catalog as $row) {
+ $offer = new Offer();
+
+ $item = match($row['type']) {
+ 'product' => new Product(),
+ 'service' => new Service(),
+ };
+
+ $item->setName($row['name']);
+ if (!empty($row['description'])) {
+ $item->setDescription($row['description']);
+ }
+ if (!empty($row['price'])) {
+ $price = new PriceSpecification();
+ $price->setPrice($row['price']);
+ $price->setPriceCurrency('CAD');
+ $price->setUnitText($row['unitText']);
+ $offer->setPriceSpecification($price);
+ }
+ $offer->setItemOffered($item);
+ $offerCatalog[] = $offer;
+ }
+ if (!empty(!$offerCatalog)) {
+ $this->setOffers($offerCatalog);
+ }
+ }
+ }
+
+ /**
+ * This presumes that the Meta instance being passed is for a profile content type.
+ * @param Meta $meta
+ * @return void
+ */
+ public function generateOffersFromContent(Meta $meta):void
+ {
+ $userID = (int)get_post_meta($meta->id(), BASE.'profile_link', true);
+ if (empty($userID)) {
+ return;
+ }
+ $user = get_userdata($userID);
+ if (!$user) {
+ return;
+ }
+ $registrar = Registrar::getInstance(jvbUserRole($userID));
+ if (!$registrar) {
+ return;
+ }
+ $types = $registrar->getCreatable();
+ if (empty($types)) {
+ return;
+ }
+ $offers = [];
+ $cities = $meta->get('city');
+ $rate = $meta->get('rate');
+ $reviews = $meta->get('reviews');
+ $rating = $meta->get('average_rating');
+ $theCities = $theRate = $theReviews = $theRating = null;
+
+ foreach($types as $type) {
+ $based = jvbCheckBase($type);
+ $hasAny = new WP_Query([
+ 'post_type' => $based,
+ 'posts_per_page'=> 1,
+ 'fields' => 'ids',
+ 'post_author' => $userID
+ ]);
+ if ($hasAny->have_posts()) {
+ $offer = new Offer();
+ $typeRegistrar = Registrar::getInstance($type);
+
+ }
+ }
}
}
diff --git a/inc/managers/SEO/render/Traits/_Properties/openingHoursSpecificationTrait.php b/inc/managers/SEO/render/Traits/_Properties/openingHoursSpecificationTrait.php
index f9aba4a..594ae51 100644
--- a/inc/managers/SEO/render/Traits/_Properties/openingHoursSpecificationTrait.php
+++ b/inc/managers/SEO/render/Traits/_Properties/openingHoursSpecificationTrait.php
@@ -4,6 +4,8 @@
use JVBase\base\SchemaHelper;
use JVBase\managers\SEO\render\Thing\Intangible\StructuredValue\OpeningHoursSpecification;
use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+use JVBase\meta\Meta;
+use JVBase\registrar\Fields;
if (!defined('ABSPATH')) {
exit;
@@ -41,15 +43,15 @@
$this->openingHoursSpecification = $openingHoursSpecification;
}
- public function getOpeningHoursSpecificationFieldConfig():array
+ public function setOpeningHoursSpecificationField(Fields $fields):void
{
- return [
+ $fields->addField('openingHours', [
'type' => 'repeater',
- 'label' => 'Opening Hours',
+ 'label' => __('Opening Hours', 'jvb'),
'fields' => [
'dayOfWeek' => [
- 'type' => 'radio',
- 'label' => 'Day(s) of Week',
+ 'type' => 'set',
+ 'label' => __('Day(s) of Week', 'jvb'),
'options' => [
'Mo' => 'Monday',
'Tu' => 'Tuesday',
@@ -58,17 +60,65 @@
'Fr' => 'Friday',
'Sa' => 'Saturday',
'Su' => 'Sunday'
- ]
+ ],
+ 'required' => true
],
'opens' => [
'type' => 'time',
- 'label' => 'Opens At',
+ 'label' => __('Opens at', 'jvb'),
+ 'required' => true
],
'closes' => [
'type' => 'time',
- 'label' => 'Closes At',
+ 'label' => __('Closes at', 'jvb'),
+ 'required' => true
]
]
- ];
+ ]);
+ $fields->addField('by_appointment', [
+ 'type' => 'true_false',
+ 'label' => __('By Appointment Only', 'jvb'),
+ ]);
+ $fields->addField('allow_walkins', [
+ 'type' => 'true_false',
+ 'label' => __('Walk Ins Welcome', 'jvb')
+ ]);
+ }
+ public function formatOpeningHoursSpecificationField(Meta $meta):void
+ {
+ $openingHours = $meta->get('openingHours');
+
+ if (!empty($openingHours)) {
+ $used = [
+ 'Mo' => false,
+ 'Tu' => false,
+ 'We' => false,
+ 'Th' => false,
+ 'Fr' => false,
+ 'Sa' => false,
+ 'Su' => false,
+ ];
+ $hours = [];
+ foreach ($openingHours as $row) {
+ $days = array_filter(explode(',', $row['dayOfWeek']),
+ function ($d) use ($used) {
+ if ($used[$d] === false) {
+ $used[$d] = true;
+ return true;
+ }
+ return false;
+ });
+ if (empty($days)) {
+ continue;
+ }
+ $opens = new OpeningHoursSpecification();
+ $opens->setDayOfWeek($days);
+ $opens->setOpens($row['opens']);
+ $opens->setCloses($row['closes']);
+ }
+ if (!empty($hours)){
+ $this->setOpeningHoursSpecification($hours);
+ }
+ }
}
}
diff --git a/inc/managers/SEO/render/Traits/_Properties/paymentAcceptedTrait.php b/inc/managers/SEO/render/Traits/_Properties/paymentAcceptedTrait.php
index fd7ced0..5dd3c7c 100644
--- a/inc/managers/SEO/render/Traits/_Properties/paymentAcceptedTrait.php
+++ b/inc/managers/SEO/render/Traits/_Properties/paymentAcceptedTrait.php
@@ -2,6 +2,8 @@
namespace JVBase\managers\SEO\render\Traits\_Properties;
use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+use JVBase\meta\Meta;
+use JVBase\registrar\Fields;
if (!defined('ABSPATH')) {
exit;
@@ -25,12 +27,31 @@
$this->paymentAccepted = $paymentAccepted;
}
- public function getPaymentAcceptedFieldConfig():array
+ public function setPaymentAcceptedField(Fields $fields):void
{
- return [
- 'type' => 'string',
- 'label' => 'Payment Accepted',
- 'hint' => 'A comma separated list of payment accepted, example: Cash, Credit Card, Cryptocurrency, Local Exchange Tradings System, etc.'
- ];
+ $fields->addField(
+ 'payment_accepted',
+ [
+ 'type' => 'set',
+ 'label' => __('Payment Accepted', 'jvb'),
+ 'options' => [
+ 'Cash' => 'Cash',
+ 'Credit Card' => 'Credit Card',
+ 'Debit' => 'Debit',
+ 'Google Pay' => 'Google Pay',
+ 'Apple Pay' => 'Apple Pay',
+ 'PayPal' => 'PayPal',
+ 'Interac' => 'Interac',
+ 'AMEX' => 'AMEX',
+ ],
+ ]
+ );
+ }
+ public function formatPaymentAcceptedField(Meta $meta):void
+ {
+ $accepted = $meta->get('payment_accepted');
+ if (!empty($accepted)){
+ $this->setPaymentAccepted($accepted);
+ }
}
}
diff --git a/inc/managers/SEO/render/Traits/_Properties/photoTrait.php b/inc/managers/SEO/render/Traits/_Properties/photoTrait.php
index 883984a..ebe9e43 100644
--- a/inc/managers/SEO/render/Traits/_Properties/photoTrait.php
+++ b/inc/managers/SEO/render/Traits/_Properties/photoTrait.php
@@ -3,6 +3,8 @@
use JVBase\managers\SEO\render\Thing\CreativeWork\MediaObject\ImageObject;
use JVBase\managers\SEO\render\Thing\CreativeWork\Photograph;
+use JVBase\meta\Meta;
+use JVBase\registrar\Fields;
if (!defined('ABSPATH')) {
exit;
@@ -31,4 +33,26 @@
'hint' => 'A photograph of this place.'
];
}
+ public function setPhotoField(Fields $fields):void
+ {
+ $fields->addField(
+ 'outside_photo',
+ [
+ 'type' => 'image',
+ 'limit' => 1,
+ 'label' => __('Outside Photo', 'jvb')
+ ]
+ );
+ }
+
+ public function formatPhotoField(Meta $meta):void
+ {
+ $image = $meta->get('outside_photo');
+ if (!empty($image) && $image > 0) {
+ $image = $this->createImageFromID($image);
+ if ($image) {
+ $this->setPhoto($image);
+ }
+ }
+ }
}
diff --git a/inc/managers/SEO/render/Traits/_Properties/reviewTrait.php b/inc/managers/SEO/render/Traits/_Properties/reviewTrait.php
index 2f5a238..db6bcf1 100644
--- a/inc/managers/SEO/render/Traits/_Properties/reviewTrait.php
+++ b/inc/managers/SEO/render/Traits/_Properties/reviewTrait.php
@@ -2,7 +2,11 @@
namespace JVBase\managers\SEO\render\Traits\_Properties;
use JVBase\managers\SEO\render\Thing\CreativeWork\Review;
+use JVBase\managers\SEO\render\Thing\Intangible\Rating\Rating;
+use JVBase\managers\SEO\render\Thing\Person\Person;
use JVBase\managers\SEO\render\Traits\_Helpers\arrayHelper;
+use JVBase\meta\Meta;
+use JVBase\registrar\Fields;
if (!defined('ABSPATH')) {
exit;
@@ -64,4 +68,156 @@
]
];
}
+ public function setReviewField(Fields $fields):void
+ {
+ $fields->addField(
+ 'enable_gmb_review_sync',
+ [
+ 'type' => 'true_false',
+ 'label' => __('Enable Google My Business Sync', 'jvb'),
+ 'hint' => 'Automagically fetch your latest positive reviews from your Google Business Listing.
+ Note: you must enable your GMB integration in settings.',
+ ]
+ );
+ $fields->addField('gmb_min_rating',
+ [
+ 'type' => 'number',
+ 'default' => 4,
+ 'min' => 1,
+ 'max' => 5,
+ 'label' => 'Minimum Rating to fetch',
+ 'hint' => 'Defaults to 4 stars and above',
+ 'condition' => [
+ 'field' => 'enable_gmb_review_sync',
+ 'operator' => '==',
+ 'value' => true,
+ ]
+ ]
+ );
+ $fields->addField(
+ 'reviews',
+ [
+ 'type' => 'repeater',
+ 'label' => __('Reviews', 'jvb'),
+ 'fields' => [
+ 'author' => [
+ 'type' => 'text',
+ 'label' => __('Review Author', 'jvb'),
+ 'required' => true,
+ ],
+ 'reviewRating' => [
+ 'type' => 'number',
+ 'min' => 1,
+ 'max' => 5,
+ 'label' => __('Review Rating', 'jvb'),
+ 'required' => true,
+ ],
+ 'reviewBody' => [
+ 'type' => 'textarea',
+ 'label' => __('Review Text Content', 'jvb'),
+ ],
+ 'url' => [
+ 'type' => 'url',
+ 'label' => __('Link to actual review'),
+ 'hint' => 'Optional. Link directly to the review source for added authenticity.'
+ ]
+ ],
+ 'condition' => [
+ 'field' => 'enable_gmb_review_sync',
+ 'operator' => '!=',
+ 'value' => true,
+ ]
+ ]
+ );
+ }
+
+ public function formatReviewField(Meta $meta):void
+ {
+ $gmb = $meta->get('enable_gmb_review_sync');
+ if (!empty($gmb) && $gmb === true) {
+ $this->fetchGMBReviews($meta);
+ return;
+ }
+
+ $reviews = $meta->get('reviews');
+ if (!empty($reviews)) {
+ $theReviews = [];
+ foreach ($reviews as $row) {
+ $review = new Review();
+ $author = $row['author'];
+ $reviewer = new Person();
+ $reviewer->setName($author);
+ $review->setAuthor($reviewer);
+ $rating = new Rating();
+ $rating->setRatingValue($row['reviewRating']);
+ $review->setReviewRating($rating);
+ if (!empty($row['reviewBody'])) {
+ $review->setReviewBody($row['reviewBody']);
+ }
+ if (!empty($row['url'])){
+ $review->setUrl($row['url']);
+ }
+ $theReviews[] = $review;
+ }
+ if (!empty($theReviews)) {
+ $this->setReview($theReviews);
+ }
+ }
+ }
+
+ protected function fetchGMBReviews(Meta $meta):void
+ {
+ $userID = (int) get_post_meta($meta->id(), BASE.'profile_link', true);
+ if ($userID && $userID > 0) {
+ $integration = JVB()->connect('gmb', $userID);
+ if ($integration && $integration->isSetUp()){
+ $fetched = $integration->getReviews(20);
+
+ if (!empty($fetched)) {
+ $reviews = [];
+ $minRating = $meta->get('gmb_min_rating');
+ foreach ($fetched as $review) {
+ $rating = $review['starRating'] ?? 0;
+ if ($rating >= $minRating) {
+ $reviews[] = $review;
+ if (count($reviews) >= 10) {
+ break;
+ }
+ }
+ }
+ }
+ if (!empty($reviews)) {
+ $theReviews = [];
+ foreach ($reviews as $r) {
+ $review = new Review();
+ $author = $r['reviewer']['displayName'] ?? 'Anonymous';
+ $author = strtok($author, ' ');
+ $reviewer = new Person();
+ $reviewer->setName($author);
+ $review->setAuthor($reviewer);
+ $rating = new Rating();
+ $rating->setRatingValue(match($r['starRating']) {
+ 'FIVE' => 5,
+ 'FOUR' => 4,
+ 'THREE' => 3,
+ 'TWO' => 2,
+ 'ONE' => 1,
+ default => 0
+ });
+ $review->setReviewRating($rating);
+ if (!empty($r['comment'])) {
+ $review->setReviewBody($r['comment']);
+ }
+ if (!empty($r['updateTime']??'')){
+ $review->setDateCreated($r['updateTime']);
+ }
+ $theReviews[] = $review;
+ }
+ if (!empty($theReviews)) {
+ $this->setReview($theReviews);
+ }
+ }
+ }
+ }
+ }
}
diff --git a/inc/managers/SEO/render/Traits/_Properties/sloganTrait.php b/inc/managers/SEO/render/Traits/_Properties/sloganTrait.php
index dca604c..1a95375 100644
--- a/inc/managers/SEO/render/Traits/_Properties/sloganTrait.php
+++ b/inc/managers/SEO/render/Traits/_Properties/sloganTrait.php
@@ -1,6 +1,8 @@
<?php
namespace JVBase\managers\SEO\render\Traits\_Properties;
+use JVBase\registrar\Fields;
+
if (!defined('ABSPATH')) {
exit;
}
@@ -18,4 +20,15 @@
{
$this->slogan = $slogan;
}
+
+ public function setSloganField(Fields $fields):void
+ {
+ $fields->addField(
+ 'slogan',
+ [
+ 'type' => 'text',
+ 'label' => __('Tagline or Slogan', 'jvb')
+ ]
+ );
+ }
}
diff --git a/inc/registrar/Fields.php b/inc/registrar/Fields.php
index ada4615..f310a3f 100644
--- a/inc/registrar/Fields.php
+++ b/inc/registrar/Fields.php
@@ -1,6 +1,8 @@
<?php
namespace JVBase\registrar;
+use JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness;
+use JVBase\managers\SEO\render\Thing\Thing;
use JVBase\registrar\fields\Field;
use JVBase\registrar\fields\GroupedField;
use JVBase\registrar\fields\OptionsField;
@@ -267,159 +269,97 @@
protected function addReviewField(?string $label = null):void
{
- $this->addField(
- 'reviews',
- [
- 'type' => 'repeater',
- 'add_label' => 'name',
- 'label' => $label ?: 'Reviews',
- 'fields' => [
- 'name' => [
- 'type' => 'text',
- 'label' => 'Reviewer Name',
- ],
- 'review' => [
- 'type' => 'textarea',
- 'quill' => false,
- 'label' => 'Review',
- ],
- 'rating' => [
- 'type' => 'select',
- 'label' => 'Rating',
- 'options' => [
- 'none' => 'Not Given',
- '0.5' => '0.5',
- '1' => '1',
- '1.5' => '1.5',
- '2' => '2',
- '2.5' => '2.5',
- '3' => '3',
- '3.5' => '3.5',
- '4' => '4',
- '4.5' => '4.5',
- '5' => '5',
- ],
- 'default' => 'none'
- ],
- 'date' => [
- 'type' => 'date',
- 'label' => 'Date of Review',
- ],
- 'url' => [
- 'type' => 'url',
- 'label' => 'Link to Review (optional)',
- ],
- ],
- 'section' => 'seo'
- ]
- );
+ $biz = new LocalBusiness();
+ $biz->setReviewField($this);
}
protected function addAlternateNameField():void
{
- $this->addField(
- 'alternate_name',
- [
- 'type' => 'repeater',
- 'label' => 'Alternate Name',
- 'fields' => [
- 'name' => [
- 'type' => 'text',
- 'label' => 'Name',
- ]
- ],
- 'section' => 'seo'
- ]
- );
+ $thing = new Thing();
+ $thing->setAlternateNameField($this);
}
protected function addKeywordsField():void
{
- $this->addField(
- 'keywords',
- [
- 'type' => 'repeater',
- 'label' => 'Keywords',
- 'fields' => [
- 'keyword' => [
- 'type' => 'text',
- 'label' => 'Keyword',
- ],
- ],
- 'default' => $labels ?? [ 'Edmonton tattoos', 'Edmonton tattoo artist', 'Edmonton tattooist' ],
- 'section' => 'seo',
- 'quickEdit' => true,
- ]
- );
+ $thing = new LocalBusiness();
+ $thing->setKeywordsField($this);
}
protected function addOutsidePhotoField():void
{
- $this->addField(
- 'outside_photo',
- [
- 'type' => 'image',
- 'limit' => 1,
- 'label' => __('Outside Photo', 'jvb')
- ]
- );
+ $business = new LocalBusiness();
+ $business->setPhotoField($this);
}
protected function addSloganField():void
{
- $this->addField(
- 'slogan',
- [
- 'type' => 'text',
- 'label' => __('Tagline or Slogan', 'jvb')
- ]
- );
+ $business = new LocalBusiness();
+ $business->setSloganField($this);
}
protected function addPaymentField():void
{
- $this->addField(
- 'payment_accepted',
- [
- 'type' => 'set',
- 'label' => __('Payment Accepted', 'jvb'),
- 'options' => [
- 'Cash' => 'Cash',
- 'Credit Card' => 'Credit Card',
- 'Debit' => 'Debit',
- 'Google Pay' => 'Google Pay',
- 'Apple Pay' => 'Apple Pay',
- 'PayPal' => 'PayPal',
- 'Interac' => 'Interac',
- 'AMEX' => 'AMEX',
- ],
- ]
- );
+ $business = new LocalBusiness();
+ $business->setPaymentAcceptedField($this);
}
protected function addAmenitiesField():void
{
+ $business = new LocalBusiness();
+ $business->setAmenityFeatureField($this);
+ }
+ protected function addCredentialsField():void
+ {
$this->addField(
- 'amenities',
+ 'credentials',
[
'type' => 'set',
- 'label' => __('Amenities', 'jvb'),
- 'options' => [
- 'Wheelchair Accessible' => 'Wheelchair Accessible',
- 'Free Parking' => 'Free Parking',
- 'Private Rooms' => 'Private Rooms',
- 'Air Conditioning' => 'Air Conditioning',
- 'WiFi' => 'WiFi',
- 'Gender Neutral Restroom' => 'Gender Neutral Restroom',
- 'LGBTQ+ Friendly' => 'LGBTQ+ Friendly',
- 'Sterilization Room' => 'Sterilization Room',
- 'Refreshments Available' => 'Refreshments Available',
- 'Street Level Access' => 'Street Level Access',
- 'Single Use Needles' => 'Single Use Needles',
- 'Consultation Room' => 'Consultation Room',
- 'Aftercare Products Available' => 'Aftercare Products Available',
- 'Walk-Ins Welcome' => 'Walk-Ins Welcome',
+ 'label' => __('Credentials', 'jvb'),
+ 'options'=> [
+ 'WHMIS 2015' => 'WHMIS 2015',
+ 'Tattoo and Piercing Safety Standards' => 'Tattoo and Piercing Safety Standards',
+ 'Bloodborne Pathogens and Infection Control' => 'Bloodborne Pathogens and Infection Control',
+ 'First Aid Training' => 'First Aid Training',
]
]
);
}
+ protected function addPermanentlyCloseField():void
+ {
+ $business = new LocalBusiness();
+ $business->setDissolutionDateField($this);
+ }
+
+ protected function addHoursField():void
+ {
+ $business = new LocalBusiness();
+ $business->setOpeningHoursSpecificationField($this);
+ }
+
+ protected function addRateField():void
+ {
+ $this->addField('rate',
+ [
+ 'type' => 'text',
+ 'subtype' => 'number',
+ 'label' => __('Hourly Rate', 'jvb')
+ ]);
+ }
+
+ protected function addAwardsField():void
+ {
+ $business = new LocalBusiness();
+ $business->setAwardField($this);
+ }
+
+ protected function addRatingsField():void
+ {
+ $business = new LocalBusiness();
+ $business->setAggregateRatingField($this);
+ }
+
+ protected function addServicesField():void
+ {
+ $business = new LocalBusiness();
+ $business->setHasOfferCatalogField($this);
+ }
}
--
Gitblit v1.10.0