| | |
| | | <?php |
| | | namespace JVBase\base; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\registrar\config\seo\Resolver; |
| | | use JVBase\registrar\Registrar; |
| | | |
| | | class SchemaHelper |
| | | { |
| | | protected static array $allowedTypes; |
| | | protected static array $allowedFormats = ['schema', 'archive', 'meta', 'reference']; |
| | | protected static array $schemas = []; |
| | | protected static array $metas = []; |
| | | protected static array $archives = []; |
| | | protected static array $references = []; |
| | | public function __construct() |
| | | { |
| | | self::$allowedTypes = array_merge(['website', 'organization'], Registrar::getRegistered()); |
| | | } |
| | | public static function checkType(string $type, string $reference = '[SchemaHelper] Invalid type'):string|false |
| | | { |
| | | $type = strtolower($type); |
| | | if (!in_array($type, self::$allowedTypes)) { |
| | | error_log($reference.': '.$type); |
| | | return false; |
| | | } |
| | | return $type; |
| | | } |
| | | public static function checkFormat(string $format, string $reference = '[SchemaHelper] Invalid format'):string|false |
| | | { |
| | | $format = strtolower($format); |
| | | if (!in_array($format, self::$allowedFormats)) { |
| | | error_log($reference.': '.$format); |
| | | return false; |
| | | } |
| | | return $format; |
| | | } |
| | | public static function getConfig(string $type, string $format):array |
| | | { |
| | | $reference = '[SchemaHelper]::getConfig'; |
| | | $type = self::checkType($type, $reference); |
| | | $format = self::checkFormat($format, $reference); |
| | | if (!$type || !$format) { |
| | | return []; |
| | | } |
| | | return match($format) { |
| | | 'schema' => self::schema($type), |
| | | 'archive' => self::archive($type), |
| | | 'meta' => self::meta($type), |
| | | 'reference' => self::reference($type), |
| | | }; |
| | | } |
| | | public static function schema(string $type): array |
| | | { |
| | | $type = self::checkType($type, '[SchemaHelper]::schema'); |
| | | if (!$type) { |
| | | return []; |
| | | } |
| | | |
| | | if (!array_key_exists($type, self::$schemas)) { |
| | | self::$schemas[$type] = get_option(BASE.ucfirst($type).'Schema', self::getDefault($type, 'schema')); |
| | | } |
| | | return self::$schemas[$type]; |
| | | } |
| | | public static function meta(string $type): array |
| | | { |
| | | $type = self::checkType($type, '[SchemaHelper]::meta'); |
| | | if (!$type) { |
| | | return []; |
| | | } |
| | | if (!array_key_exists($type, self::$metas)) { |
| | | self::$metas[$type] = get_option(BASE.ucfirst($type).'Meta', self::getDefault($type, 'meta')); |
| | | } |
| | | return self::$metas[$type]; |
| | | } |
| | | public static function archive(string $type): array |
| | | { |
| | | $type = self::checkType($type, '[SchemaHelper]::archive'); |
| | | error_log('[SchemaHelper]::archive type: '.print_r($type, true)); |
| | | if (!$type) { |
| | | return []; |
| | | } |
| | | |
| | | if (!array_key_exists($type, self::$archives)) { |
| | | self::$archives[$type] = get_option(BASE.ucfirst($type).'Archive', self::getDefault($type, 'archive')); |
| | | } |
| | | return self::$archives[$type]; |
| | | } |
| | | public static function reference(string $type): array |
| | | { |
| | | $type = self::checkType($type, '[SchemaHelper]::reference'); |
| | | if (!$type) { |
| | | return []; |
| | | } |
| | | if (!array_key_exists($type, self::$references)) { |
| | | self::$references[$type] = get_option(BASE.ucfirst($type).'Reference', self::getDefault($type, 'reference')); |
| | | } |
| | | return self::$references[$type]; |
| | | } |
| | | |
| | | public static function getDefault(string $type, string $format):array |
| | | { |
| | | $reference = '[SchemaHelper]::getDefault'; |
| | | $type = self::checkType($type, $reference); |
| | | $format = self::checkFormat($format, $reference); |
| | | if (!$type || !$format) { |
| | | return []; |
| | | } |
| | | |
| | | $defaults = match ($format) { |
| | | 'schema' => match ($type) { |
| | | 'website' => [ |
| | | 'type' => 'JVBase\managers\SEO\render\Thing\CreativeWork\WebSite', |
| | | 'name' => get_bloginfo('name'), |
| | | 'url' => get_home_url(), |
| | | 'id' => get_home_url() . '#website', |
| | | 'description' => get_bloginfo('description'), |
| | | 'inLanguage' => 'en-CA' |
| | | ], |
| | | default => [] |
| | | }, |
| | | 'archive' => [ |
| | | 'type' => 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage', |
| | | ], |
| | | default => [], |
| | | }; |
| | | return apply_filters(BASE.ucfirst($type).ucfirst($format).'Default', $defaults); |
| | | } |
| | | |
| | | public static function updateHistory(string $type, string $format, array $newest):bool |
| | | { |
| | | $reference = '[SchemaHelper]::updateHistory'; |
| | | $type = self::checkType($type, $reference); |
| | | $format = self::checkFormat($format, $reference); |
| | | if (!$type || !$format) { |
| | | return false; |
| | | } |
| | | |
| | | $historyOption = BASE.ucfirst($type).ucfirst($format).'History'; |
| | | $history = get_option($historyOption, []); |
| | | array_unshift($history, $newest); |
| | | if (count($history) > 5) { |
| | | array_pop($history); |
| | | } |
| | | return update_option($historyOption, $history); |
| | | } |
| | | |
| | | public static function update(string $type, string $format, array $config, ?Meta $meta = null):bool |
| | | { |
| | | $reference = '[SchemaHelper]::update'; |
| | | $type = self::checkType($type, $reference); |
| | | $format = self::checkFormat($format, $reference); |
| | | if (!$type || !$format) { |
| | | return false; |
| | | } |
| | | $method = 'update'.ucfirst($type); |
| | | return self::$method($config, $meta); |
| | | } |
| | | public static function updateSchema(string $type, array $config):bool |
| | | { |
| | | $reference = '[SchemaHelper]::updateSchema'; |
| | | $type = self::checkType($type, $reference); |
| | | if (!$type) { |
| | | return false; |
| | | } |
| | | if (!class_exists($config['type'])){ |
| | | error_log('[SchemaHelper]::updateSchema Config must be a valid schema type: '.$config['type']); |
| | | return false; |
| | | } |
| | | if (!in_array($type, self::$allowedTypes)) { |
| | | error_log('[SchemaHelper]::updateSchema Config must have a schema type'); |
| | | } |
| | | |
| | | return self::updateClassConfig($type, 'schema', $config); |
| | | } |
| | | |
| | | public static function updateClassConfig(string $type, string $format, array $config):bool |
| | | { |
| | | $reference = '[SchemaHelper]::updateClassConfig'; |
| | | $type = self::checkType($type, $reference); |
| | | if (!$type) { |
| | | return false; |
| | | } |
| | | if (!class_exists($config['type'])){ |
| | | error_log($reference.' Config must be a valid schema type: '.$config['type']); |
| | | return false; |
| | | } |
| | | if (!in_array($type, self::$allowedTypes)) { |
| | | error_log($reference.' Config must have a schema type'); |
| | | } |
| | | //Merge stored config with updates |
| | | $stored = self::schema($type); |
| | | $update = array_merge_recursive($stored, $config); |
| | | |
| | | //Validate Properties |
| | | $className = $update['type']; |
| | | unset($update['type']); |
| | | foreach ($update as $property => $value) { |
| | | if (!property_exists($className, $property)) { |
| | | error_log($reference.' invalid property attempted: '.$property.', with value: '.print_r($value, true).' for class: '.$className); |
| | | unset($update[$property]); |
| | | } |
| | | } |
| | | $update['type'] = $className; |
| | | |
| | | //Add changes to history (keeps last 5 changes) |
| | | self::updateHistory($type, $format, $update); |
| | | self::$schemas[$type] = $update; |
| | | return update_option(BASE.ucfirst($type).ucfirst($format), $update); |
| | | } |
| | | |
| | | public static function updateMeta(string $type, array $config):bool |
| | | { |
| | | $type = self::checkType($type, '[SchemaHelper]::updateMeta'); |
| | | $allowed = array_filter($config, function($key) { |
| | | $allowed = in_array($key, ['name', 'description']); |
| | | if (!$allowed) { |
| | | error_log('[SchemaHelper]::updateMeta invalid property attempted: '.$key); |
| | | } |
| | | return $allowed; |
| | | }); |
| | | if (empty($allowed)) { |
| | | error_log('[SchemaHelper]::updateMeta Name or Description must be set'); |
| | | return false; |
| | | } |
| | | $config = array_map('sanitize_text_field', $config); |
| | | self::updateHistory($type, 'meta', $config); |
| | | self::$metas[$type] = $config; |
| | | return update_option(BASE.ucfirst($type).'Meta', $config); |
| | | } |
| | | |
| | | public static function updateArchive(string $type, array $config):bool |
| | | { |
| | | $reference = '[SchemaHelper]::updateArchive'; |
| | | $type = self::checkType($type, $reference); |
| | | if (!$type) { |
| | | return false; |
| | | } |
| | | if (!class_exists($config['type'])){ |
| | | error_log('[SchemaHelper]::updateSchema Config must be a valid schema type: '.$config['type']); |
| | | return false; |
| | | } |
| | | if (!in_array($type, self::$allowedTypes)) { |
| | | error_log('[SchemaHelper]::updateSchema Config must have a schema type'); |
| | | } |
| | | |
| | | return self::updateClassConfig($type, 'schema', $config); |
| | | } |
| | | |
| | | public static function classFromConfig(array $config, ?Meta $meta = null):mixed |
| | | { |
| | | if (!array_key_exists('type', $config)) { |
| | | error_log('[SchemaHelper]::classFromConfig No class defined in config: '.print_r($config, true)); |
| | | return false; |
| | | } |
| | | $className = $config['type']; |
| | | unset($config['type']); |
| | | $class = new $className(); |
| | | |
| | | foreach ($config as $property => $value) { |
| | | if (is_array($value)) { |
| | | $value = self::classFromConfig($value, $meta); |
| | | } |
| | | $method = 'set'.ucfirst($property); |
| | | if (!method_exists($class, $method)) { |
| | | error_log('[SchemaHelper]::classFromConfig - method: '.$method.' does not exist in class: '.$className); |
| | | continue; |
| | | } |
| | | if (is_string($value) && str_contains($value, '{{')) { |
| | | $value = Resolver::resolveForSchema($property, $value, $config, $meta); |
| | | } |
| | | if (!empty($value)) { |
| | | $class->$method($value); |
| | | } |
| | | } |
| | | return $class; |
| | | } |
| | | } |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | |
| | | /** |
| | | * JVB_SCHEMA: Site-wide schema configuration |
| | | * |
| | |
| | | * - attribution: Developer/maintainer info |
| | | */ |
| | | |
| | | use JVBase\managers\SEO\SchemaBuilder; |
| | | |
| | | $schema = apply_filters('jvb_schema', []); |
| | | $registry = SchemaBuilder::getInstance(); |
| | | $checked = []; |
| | | foreach ($schema as $key => $config) { |
| | | |
| | | if (array_key_exists('type', $config)) { |
| | | $type = $config['type']; |
| | | } elseif ($key === 'website') { |
| | | $type = 'WebSite'; |
| | | } |
| | | $exists = !is_null($registry->getTypeDefinition($type)); |
| | | if (!$exists) { |
| | | // error_log('[JVB_SCHEMA] No definitions for: '.print_r($type, true)); |
| | | continue; |
| | | } |
| | | $allowed = $registry->getFieldsForType($type); |
| | | $filtered = array_filter($config, function ($item) use ($allowed) { |
| | | return in_array($item, $allowed); |
| | | }, ARRAY_FILTER_USE_KEY); |
| | | |
| | | if (empty($filtered)) { |
| | | // error_log('[JVB_SCHEMA] No valid filters for '.$type.'.'); |
| | | continue; |
| | | } |
| | | $removed = array_filter($config, function ($item) use ($allowed) { |
| | | return !in_array($item, $allowed); |
| | | }, ARRAY_FILTER_USE_KEY); |
| | | |
| | | if (!empty($removed)) { |
| | | // error_log('[JVB_SCHEMA] Invalid fields detected for '.$type.': '.print_r($removed, true)); |
| | | } |
| | | $checked[$key] = $filtered; |
| | | } |
| | | |
| | | define('JVB_SCHEMA', $checked); |
| | | |
| | | |
| | | /** |
| | | JVB_CONTENT['artwork'] = [ |