<?php
|
namespace JVBase\admin;
|
|
use JVBase\meta\Form;
|
use JVBase\meta\Sanitizer;
|
use JVBase\registrar\Registrar;
|
use JVBase\ui\Tabs;
|
|
if (!defined('ABSPATH')) {
|
exit;
|
}
|
|
class SEOAdmin {
|
protected string $admin_page;
|
protected array $types = [
|
'JVBase\managers\SEO\render\Thing' => '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
|
{
|
?>
|
<div class="wrap jvb-seo-admin">
|
<h1><?= jvbDashIcon('magnifying-glass'); ?> SEO Configuration</h1>
|
|
<?php
|
$tabs = new Tabs();
|
|
$tabs->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();
|
?>
|
</div>
|
|
<?php
|
if ($outputScripts) {
|
$this->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(
|
'<form action="%s" method="POST">
|
<input type="hidden" name="action" value="%s">
|
<input type="hidden" name="%s_nonce" value="%s">',
|
esc_url(admin_url('admin-post.php')),
|
$action,
|
$action,
|
wp_create_nonce($action)
|
);
|
}
|
protected function formEnd():void
|
{
|
echo '<button type="submit">Save Changes</button>
|
</form>';
|
}
|
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'
|
];
|
}
|
}
|