'Thing', 'JVBase\managers\SEO\render\Thing\CreativeWork\CreativeWork' => ' - Creative Work', 'JVBase\managers\SEO\render\Thing\CreativeWork\CategoryCodeSet' => '- - Category Code Set', 'JVBase\managers\SEO\render\Thing\CreativeWork\Certification' => '- - Certification', 'JVBase\managers\SEO\render\Thing\CreativeWork\Clip' => ' - - Clip', 'JVBase\managers\SEO\render\Thing\CreativeWork\DefinedTermSet' => ' - - Defined Term Set', 'JVBase\managers\SEO\render\Thing\CreativeWork\HowTo' => ' - - How To', 'JVBase\managers\SEO\render\Thing\CreativeWork\Menu' => ' - - Menu', 'JVBase\managers\SEO\render\Thing\CreativeWork\MenuSection' => ' - - Menu Section', 'JVBase\managers\SEO\render\Thing\CreativeWork\MusicRecording' => ' - - Music Recording', 'JVBase\managers\SEO\render\Thing\CreativeWork\Photograph' => ' - - Photograph', 'JVBase\managers\SEO\render\Thing\CreativeWork\Review' => ' - - Review', 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\Webpage' => ' - - WebPage', 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\AboutPage' => ' - - - About Page', 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\CheckoutPage' => ' - - - Checkout Page', 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\CollectionPage' => ' - - - Collection Page', 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\ContactPage' => ' - - - Contact Page', 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\FAQPage' => ' - - - FAQ Page', 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\ItemPage' => ' - - - Item Page', 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\MedicalWebPage' => ' - - - Medical Web Page', 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\ProfilePage' => ' - - - Profile Page', 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\QAPage' => ' - - - QA Page', 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\RealEstateListing' => ' - - - Real Estate Listing', 'JVBase\managers\SEO\render\Thing\CreativeWork\WebPage\SearchResultsPage' => ' - - - Search Results Page', 'JVBase\managers\SEO\render\Thing\CreativeWork\WebSite' => ' - - - Website', 'JVBase\managers\SEO\render\Thing\Event\Event' => ' - Event', 'JVBase\managers\SEO\render\Thing\Organization\Organization' => ' - Organization', 'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\LocalBusiness' => ' - - Local Business', 'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment' => ' - - - Food Establishment', 'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\Bakery' => ' - - - - Bakery', 'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\BarOrPub' => ' - - - - Bar or Pub', 'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\Brewery' => ' - - - - Brewery', 'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\CafeOrCoffeeShop' => ' - - - - Cafe or Coffee Shop', 'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\Distillery' => ' - - - - Distillery', 'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\FastFoodRestaurant' => ' - - - - Fast Food Restaurant', 'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\IceCreamShop' => ' - - - - Ice Cream Shop', 'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\Restaurant' => ' - - - - Restaurant', 'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\FoodEstablishment\Winery' => ' - - - - Winery', 'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\HealthAndBeautyBusiness' => ' - - - Health & Beauty Business', 'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\MedicalBusiness\MedicalBusiness' => ' - - - Medical Business', 'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\ProfessionalService' => ' - - - Professional Service', 'JVBase\managers\SEO\render\Thing\Organization\LocalBusiness\Store' => ' - - - Store', 'JVBase\managers\SEO\render\Thing\Person' => ' - Person', 'JVBase\managers\SEO\render\Thing\Place' => ' - Place', 'JVBase\managers\SEO\render\Thing\Product' => ' - Product', ]; protected array $isHidden = [ 'additionalType', 'mainEntityOfPage', 'subjectOf', 'brand', 'companyRegistration', 'contactPoint', 'department', 'duns', 'employee', 'ethicsPolicy', 'event', 'founder', 'funder', 'funding', 'globalLocationNumber', 'hasMerchantReturnPolicy', 'hasPOS', 'interactionStatistic', 'knowsLanguage', 'makesOffer', 'member', 'memberOf', 'ownershipFundingInfo', 'sponsor', 'containsInPlace', 'containsPlace', 'openingHours' ]; protected array $hints = [ 'alternateName' => 'An alias for the item.', 'description' => 'A description of the item.', 'disambiguatingDescription' => 'A sub property of description. A short description of the item used to disambiguate from other, similar items. Information from other properties (in particular, name) may be necessary for the description to be useful for disambiguation.', 'image' => 'An image of the item.', 'name' => 'The name of the item.', 'owner' => 'A person or organization who owns this Thing.', 'slogan' => 'A slogan or motto associated with the item.' ]; protected array $checks; public function __construct() { $this->setChecks(); add_filter('jvbDashboardPage', [$this, 'addDashboardSection'], 20, 2); add_action('admin_menu', [$this, 'addAdminMenu']); $this->addFormListeners(); } protected function setChecks():void { $checks = [ 'website' ]; $this->checks = array_merge($checks, Registrar::getRegistered()); } protected function addFormListeners():void { foreach ($this->checks as $check){ $based = jvbCheckBase($check); add_action('admin_post_'.$based, [$this, 'handleAJAXFormSubmit']); } } public function addAdminMenu():void { $main = JVB()->admin()->getMainConfig(); $this->admin_page = add_submenu_page( $main['menu_slug'], 'SEO', 'SEO', 'manage_options', 'jvb-seo', [$this, 'renderAdminPage'] ); } public function addDashboardSection(string $content, string $page):string { if ($page !== 'jvb-seo') { return $content; } ob_start(); $this->renderAdminPage(); return ob_get_clean(); } public function renderAdminPage(bool $outputScripts = true):void { ?>

SEO Configuration

addTab('website') ->title('Website') ->icon('globe-simple') ->content($this->renderWebsite()); $tabs->addTab('business') ->title('Organization') ->icon('storefront') ->content($this->renderOrganization()); // if (count(Registrar::getRegistered('post')) > 0) { // $tabs->addTab('content') // ->title('Content') // ->icon('note') // ->content($this->renderConfig('content')); // } // // // if (count(Registrar::getRegistered('taxonomy')) > 0) { // $tabs->addTab('taxonomies') // ->title('Taxonomies') // ->icon('tag') // ->content($this->renderConfig('taxonomy')); // } // // if (count(Registrar::getRegistered('user')) > 0) { // $tabs->addTab('user-roles') // ->title('User Roles') // ->icon('users-three') // ->content($this->renderConfig('taxonomy')); // } echo $tabs->render(); ?>
renderStyles(); } } public function renderProperty(string $property, ?string $value, mixed $class):void { $method = 'get'.ucFirst($property).'FieldConfig'; $config = method_exists($class, $method) ? $class->$method() : $this->buildConfig($property); if(!array_key_exists('type', $config)) { error_log('Invalid Config for '.$property.': '.print_r($config, true)); } echo Form::render($property, $value, $config); } protected function buildConfig($property) { return [ 'type' => $this->getPropertyType($property), 'label' => $this->getLabel($property) ]; } protected function getPropertyType(string $property):string { return match($property) { default => 'text' }; } protected function getLabel(string $property):string { $data = preg_split('/(?=[A-Z])/', $property); $string = implode(' ', $data); return ucwords($string); } public function renderSchemaTypeSelection(?string $value = null):void { $config = [ 'type' => 'select', 'label' => 'Schema Type', 'hint' => 'Save changes & refresh after changing.', 'options' => $this->types ]; echo Form::render('type', $value, $config); } public function renderWebsite():string { ob_start(); $this->formStart('website'); $default = 'JVBase\managers\SEO\render\Thing\CreativeWork\WebSite'; // $this->renderSchemaTypeSelection($value); $stored = get_option(BASE.'WebsiteSchema',apply_filters(BASE.'websiteSchema', [])); $this->renderFieldsFor($default, $stored); $this->formEnd(); return ob_get_clean(); } public function renderOrganization():string { ob_start(); $this->formStart('organization'); $default = apply_filters(BASE.'OrganizationSchemaType', 'JVBase\managers\SEO\render\Thing\Organization\Organization'); $value = get_option(BASE.'OrganizationSchemaType', $default); $this->renderSchemaTypeSelection($value); $stored = get_option(BASE.'OrganizationSchema',apply_filters(BASE.'OrganizationSchema', [])); $this->renderFieldsFor($value, $stored); $this->formEnd(); return ob_get_clean(); } protected function formStart(string $action):void { $action = jvbCheckBase($action); echo sprintf( '
', esc_url(admin_url('admin-post.php')), $action, $action, wp_create_nonce($action) ); } protected function formEnd():void { echo '
'; } public function renderFieldsFor(string $class, array $stored):void { $fields = $this->getFieldsFor($class); $instance = new $class(); foreach ($fields as $property => $value) { $this->renderProperty($property, $stored[$property]??null, $instance); } } public function getFieldsFor(string $class):array { if (!class_exists($class)) { error_log('Class not found: '.$class); return []; } $class = new $class(); return array_filter($class->getProperties(), function ($item) { return !in_array($item, $this->isHidden); }, ARRAY_FILTER_USE_KEY); } public function handleAJAXFormSubmit() { $action = $_POST['action']; if (!isset($_POST[$action.'_nonce']) || !wp_verify_nonce($_POST[$action.'_nonce'], $action)) { wp_die( __( 'Invalid nonce specified', 'jvb' ), __( 'Error', 'jvb' ), array( 'response' => 403, 'back_link' => 'admin.php?page=jvb-seo', ) ); } $action = jvbNoBase($action); error_log('[SEOAdmin]Post request: '.print_r($_POST, true)); if ($action !== 'website'){ $type = sanitize_text_field(stripslashes($_POST['type'])); if (empty($type) || !array_key_exists($type, $this->types)){ error_log('Invalid type: '.print_r($type, true)); return; } update_option(BASE.ucfirst($action).'SchemaType', $type); } else { $type = 'JVBase\managers\SEO\render\Thing\CreativeWork\WebSite'; } $result = $this->saveFields($action, $type, $_POST); // redirect the user to the appropriate page wp_redirect(esc_url_raw(add_query_arg($result, admin_url('admin.php?page=jvb-seo') ))); exit; } public function saveFields(string $action, string $class, array $data):array { if (!in_array($action, $this->checks)) { error_log('[SEOAdmin]Action is not allowed: '.$action); return [ 'jvb_notice' => 'error', 'jvb_message' => 'Action is not allowed: '.$action ]; } $allowed = $this->getFieldsFor($class); if (empty($allowed)) { return [ 'jvb_notice' => 'error', 'jvb_message' => 'Could not get fields from class' ]; } $checked = array_filter($data, function ($item) use ($allowed) { return array_key_exists($item, $allowed); }, ARRAY_FILTER_USE_KEY); $stored = get_option(BASE.ucfirst($action).'Schema', []); $updates = []; foreach ($checked as $property => $value) { $sanitized = Sanitizer::sanitize($value, $this->buildConfig($property)); if (!array_key_exists($property, $stored) || $stored[$property] !== $sanitized) $updates[$property] = $sanitized; } if (!empty($updates)) { $history = get_option(BASE.ucfirst($action).'SchemaHistory', []); array_unshift($history, $stored); if (count($history) > 5){ array_pop($history); } update_option(BASE.ucfirst($action).'SchemaHistory', $history); $update = array_merge($stored, $updates); update_option(BASE.ucfirst($action).'Schema', $update); } return [ 'jvb_notice' => 'success', 'jvb_message' => 'Saved changes successfully' ]; } }