From 9f86429a1252b45c95b7c62fbaa1b82de3723997 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Mon, 05 Jan 2026 18:16:07 +0000
Subject: [PATCH] =Complete TaxonomySelector.js and TaxonomyCreator.js refactor
---
inc/utility/Validator.php | 243 ++++++++++++++++++++++++++++++++++++++++++++++++
1 files changed, 242 insertions(+), 1 deletions(-)
diff --git a/inc/utility/Validator.php b/inc/utility/Validator.php
index 6cd291f..f77dd52 100644
--- a/inc/utility/Validator.php
+++ b/inc/utility/Validator.php
@@ -12,6 +12,24 @@
{
private array $errors = [];
private array $warnings = [];
+ protected array $validSchemaTypes = [
+ 'content' => [
+ 'Article', 'NewsArticle', 'BlogPosting', 'VisualArtwork',
+ 'Product', 'Service', 'Event', 'Person', 'CreativeWork',
+ 'MedicalProcedure', 'HowTo', 'Recipe', 'Review',
+ ],
+ 'taxonomy' => [
+ 'CollectionPage', 'DefinedTerm', 'ItemList',
+ ],
+ 'user' => [
+ 'Person',
+ ],
+ ];
+
+ protected array $validModifiers = [
+ 'first', 'last', 'join', 'truncate', 'strip', 'lower', 'upper',
+ 'title', 'count', 'get', 'default', 'date', 'image_url', 'excerpt', 'plural'
+ ];
public function validateAll():array
{
@@ -20,6 +38,8 @@
$success['terms'] = $this->validateTaxonomyConfig(JVB_TAXONOMY);
$success['user'] = $this->validateUserConfig(JVB_USER);
$success['crossReference'] = $this->validateCrossReferences(JVB_CONTENT, JVB_TAXONOMY, JVB_USER);
+ $success['seo'] = $this->validateSEOConfig();
+ $success['schema'] = $this->validateSchemaConfig(JVB_SCHEMA ?? []);
return $success;
}
/**
@@ -322,7 +342,7 @@
$validTypes = [
'text', 'textarea', 'number', 'email', 'url', 'select',
'radio', 'checkbox', 'true_false', 'date', 'time',
- 'datetime', 'color', 'image', 'file', 'gallery',
+ 'datetime', 'color', 'upload', 'image', 'file', 'gallery',
'repeater', 'location', 'user', 'taxonomy', 'set'
];
@@ -499,4 +519,225 @@
}
}
}
+
+ /**
+ * Validate SEO configurations across all types
+ */
+ public function validateSEOConfig(): bool
+ {
+ $this->errors = [];
+ $this->warnings = [];
+
+ foreach (JVB_CONTENT ?? [] as $slug => $config) {
+ if (isset($config['seo'])) {
+ $this->validateTypeSEOConfig($slug, $config['seo'], 'content', $config);
+ }
+ }
+
+ foreach (JVB_TAXONOMY ?? [] as $slug => $config) {
+ if (isset($config['seo'])) {
+ $this->validateTypeSEOConfig($slug, $config['seo'], 'taxonomy', $config);
+ }
+ }
+
+ foreach (JVB_USER ?? [] as $slug => $config) {
+ if (isset($config['seo'])) {
+ $this->validateTypeSEOConfig($slug, $config['seo'], 'user', $config);
+ }
+ }
+
+ $this->logResults();
+ return empty($this->errors);
+ }
+
+ /**
+ * Validate SEO config for a specific type
+ */
+ private function validateTypeSEOConfig(string $slug, array $seo, string $objectType, array $fullConfig): void
+ {
+ $path = "{$objectType}.{$slug}.seo";
+ $availableFields = $this->getAvailableSEOFields($slug, $objectType, $fullConfig);
+
+ if (isset($seo['schema_type'])) {
+ $validTypes = $this->validSchemaTypes[$objectType] ?? $this->validSchemaTypes['content'];
+ if (!in_array($seo['schema_type'], $validTypes)) {
+ $this->addWarning("{$path}.schema_type", "'{$seo['schema_type']}' may not be valid. Common types: " . implode(', ', array_slice($validTypes, 0, 5)));
+ }
+ }
+
+ if (isset($seo['field_map'])) {
+ foreach ($seo['field_map'] as $prop => $source) {
+ $this->validateFieldSource($source, $availableFields, "{$path}.field_map.{$prop}");
+ }
+ }
+
+ if (isset($seo['meta']['title'])) {
+ $this->validatePatternString($seo['meta']['title'], $availableFields, "{$path}.meta.title");
+ }
+
+ if (isset($seo['meta']['description'])) {
+ $this->validatePatternString($seo['meta']['description'], $availableFields, "{$path}.meta.description");
+ }
+ }
+
+ /**
+ * Validate a field source reference
+ */
+ private function validateFieldSource(string $source, array $availableFields, string $path): void
+ {
+ if (empty($source)) {
+ return;
+ }
+
+ if (str_contains($source, '{{')) {
+ $this->validatePatternString($source, $availableFields, $path);
+ return;
+ }
+
+ $field = explode('|', $source)[0];
+ $field = explode('.', $field)[0];
+
+ if (!in_array($field, $availableFields) && !in_array($field, ['site', 'author', 'meta', 'terms'])) {
+ $this->addWarning($path, "Field '{$field}' may not exist");
+ }
+ }
+
+ /**
+ * Validate pattern string syntax
+ */
+ private function validatePatternString(string $pattern, array $availableFields, string $path): void
+ {
+ preg_match_all('/\{\{([^}]+)\}\}/', $pattern, $matches);
+
+ foreach ($matches[1] as $token) {
+ $token = trim($token);
+
+ if (empty($token)) {
+ $this->addError($path, "Empty placeholder {{}} found");
+ continue;
+ }
+
+ $parts = explode('|', $token);
+ $field = trim(explode('.', $parts[0])[0]);
+
+ if (!in_array($field, $availableFields) && !in_array($field, ['site', 'author', 'meta', 'terms'])) {
+ $this->addWarning($path, "Field '{$field}' in pattern may not exist");
+ }
+
+ if (isset($parts[1])) {
+ $modifier = trim(explode(':', $parts[1])[0]);
+ if (!in_array($modifier, $this->validModifiers)) {
+ $this->addWarning($path, "Unknown modifier '|{$modifier}'");
+ }
+ }
+ }
+ }
+
+ /**
+ * Validate JVB_SCHEMA configuration
+ */
+ public function validateSchemaConfig(array $schema): bool
+ {
+ $this->errors = [];
+ $this->warnings = [];
+
+ if (isset($schema['business'])) {
+ $this->validateBusinessSchema($schema['business']);
+ }
+
+ if (isset($schema['faqs']['items'])) {
+ foreach ($schema['faqs']['items'] as $i => $faq) {
+ if (empty($faq['question'])) {
+ $this->addError("schema.faqs.items[{$i}].question", "FAQ question required");
+ }
+ if (empty($faq['answer'])) {
+ $this->addError("schema.faqs.items[{$i}].answer", "FAQ answer required");
+ }
+ }
+ }
+
+ $this->logResults();
+ return empty($this->errors);
+ }
+
+ /**
+ * Validate business schema
+ */
+ private function validateBusinessSchema(array $config): void
+ {
+ $path = 'schema.business';
+
+ if (empty($config['name'])) {
+ $this->addError("{$path}.name", "Business name required");
+ }
+
+ if (isset($config['url']) && !filter_var($config['url'], FILTER_VALIDATE_URL)) {
+ $this->addError("{$path}.url", "Invalid URL");
+ }
+
+ if (isset($config['email']) && !filter_var($config['email'], FILTER_VALIDATE_EMAIL)) {
+ $this->addError("{$path}.email", "Invalid email");
+ }
+
+ if (isset($config['geo'])) {
+ $lat = $config['geo']['lat'] ?? null;
+ $lng = $config['geo']['lng'] ?? null;
+
+ if ($lat !== null && (!is_numeric($lat) || $lat < -90 || $lat > 90)) {
+ $this->addError("{$path}.geo.lat", "Latitude must be -90 to 90");
+ }
+ if ($lng !== null && (!is_numeric($lng) || $lng < -180 || $lng > 180)) {
+ $this->addError("{$path}.geo.lng", "Longitude must be -180 to 180");
+ }
+ }
+
+ if (isset($config['opening_hours'])) {
+ $days = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
+ foreach ($config['opening_hours'] as $day => $data) {
+ if (!in_array(strtolower($day), $days)) {
+ $this->addWarning("{$path}.opening_hours.{$day}", "Invalid day");
+ }
+ if (is_array($data) && empty($data['closed'])) {
+ if (isset($data['open']) && !preg_match('/^\d{2}:\d{2}$/', $data['open'])) {
+ $this->addWarning("{$path}.opening_hours.{$day}.open", "Use HH:MM format");
+ }
+ }
+ }
+ }
+
+ if (isset($config['aggregate_rating'])) {
+ $value = $config['aggregate_rating']['value'] ?? null;
+ if ($value !== null && (!is_numeric($value) || $value < 0 || $value > 5)) {
+ $this->addError("{$path}.aggregate_rating.value", "Rating must be 0-5");
+ }
+ }
+
+ if (isset($config['same_as'])) {
+ foreach ($config['same_as'] as $i => $link) {
+ $url = is_array($link) ? ($link['url'] ?? '') : $link;
+ if (!empty($url) && !filter_var($url, FILTER_VALIDATE_URL)) {
+ $this->addError("{$path}.same_as[{$i}]", "Invalid URL: {$url}");
+ }
+ }
+ }
+ }
+
+ /**
+ * Get available fields for SEO validation
+ */
+ private function getAvailableSEOFields(string $slug, string $objectType, array $config): array
+ {
+ $fields = match($objectType) {
+ 'content' => ['post_title', 'post_excerpt', 'post_content', 'post_date', 'post_modified', 'post_thumbnail', 'permalink'],
+ 'taxonomy' => ['term_name', 'term_description', 'term_slug', 'permalink', 'count'],
+ 'user' => ['display_name', 'first_name', 'last_name', 'user_email', 'description', 'permalink'],
+ default => []
+ };
+
+ if (!empty($config['fields'])) {
+ $fields = array_merge($fields, array_keys($config['fields']));
+ }
+
+ return $fields;
+ }
}
--
Gitblit v1.10.0