plugin_name = $plugin_name;
$this->version = $version;
// Redirect to setup wizard on first activation.
add_action( 'admin_init', array( $this, 'maybe_redirect_to_setup' ) );
// Suppress third-party admin notices on our plugin pages.
add_action( 'in_admin_header', array( $this, 'suppress_third_party_notices' ), 1 );
// AJAX handler for verifying API key (server-side to avoid CORS).
add_action( 'wp_ajax_schema_ai_verify_key', array( $this, 'ajax_verify_key' ) );
// AJAX handler for dismissing the local-business info banner.
add_action( 'wp_ajax_schema_ai_dismiss_business_banner', array( $this, 'ajax_dismiss_business_banner' ) );
// AJAX handler for manually triggering a dashboard sync.
add_action( 'wp_ajax_schema_ai_force_sync', array( $this, 'ajax_force_sync' ) );
// Show admin notice if DB tables are still missing after auto-create.
add_action( 'admin_notices', array( $this, 'maybe_show_db_notice' ) );
}
/**
* Show an admin notice to admins if the primary DB table is missing.
*
* Fires AFTER schema_ai_run() has already attempted maybe_create_tables(),
* so if the table is still absent it means create_tables() itself failed
* (e.g. insufficient MySQL privileges). The notice tells the admin what
* happened without requiring access to debug.log.
*/
public function maybe_show_db_notice() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
global $wpdb;
$table = $wpdb->prefix . 'schema_ai_schemas';
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching, WordPress.DB.PreparedSQL.NotPrepared -- SHOW TABLES is safe; admin-only; not on user data.
$exists = $wpdb->get_var( $wpdb->prepare( 'SHOW TABLES LIKE %s', $table ) ) === $table;
if ( $exists ) {
return;
}
$last_error = $wpdb->last_error ? esc_html( $wpdb->last_error ) : __( 'Unknown error — check wp-content/debug.log', 'ai-schema-gen' );
echo '
';
echo '' . esc_html__( 'AI Schema Gen — Database Error', 'ai-schema-gen' ) . '
';
/* translators: %s: MySQL error message */
printf( esc_html__( 'The plugin\'s database tables could not be created. MySQL said: %s', 'ai-schema-gen' ), '' . $last_error . '' );
echo '
' . esc_html__( 'Please deactivate and reactivate the plugin, or contact your host to confirm your database user has CREATE TABLE privileges.', 'ai-schema-gen' );
echo '
';
}
/**
* AJAX handler: verify an API key by calling the SaaS backend server-side.
*/
public function ajax_verify_key() {
check_ajax_referer( 'schema_ai_verify_key' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'ai-schema-gen' ) ) );
}
$api_key = isset( $_POST['api_key'] ) ? sanitize_text_field( wp_unslash( $_POST['api_key'] ) ) : '';
if ( empty( $api_key ) ) {
wp_send_json_error( array( 'message' => __( 'Please enter an API key.', 'ai-schema-gen' ) ) );
}
$response = wp_remote_get(
SCHEMA_AI_API_BASE . '/auth/me',
array(
'headers' => array( 'X-API-Key' => $api_key ),
'timeout' => 15,
)
);
if ( is_wp_error( $response ) ) {
wp_send_json_error( array( 'message' => __( 'Could not connect to AI Schema Gen server.', 'ai-schema-gen' ) ) );
}
$code = wp_remote_retrieve_response_code( $response );
$body = json_decode( wp_remote_retrieve_body( $response ), true );
if ( 200 === $code && ! empty( $body['success'] ) && ! empty( $body['data']['user'] ) ) {
$plan = isset( $body['data']['user']['plan'] ) ? $body['data']['user']['plan'] : 'free';
update_user_meta( get_current_user_id(), 'schema_ai_plan', $plan );
wp_send_json_success( array( 'plan' => $plan ) );
} else {
$msg = isset( $body['error'] ) ? $body['error'] : __( 'Invalid API key.', 'ai-schema-gen' );
wp_send_json_error( array( 'message' => $msg ) );
}
}
/**
* AJAX handler: dismiss the local-business info banner permanently.
*/
public function ajax_dismiss_business_banner() {
check_ajax_referer( 'schema_ai_dismiss_business_banner' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error();
}
$settings = get_option( 'schema_ai_settings', array() );
$settings['not_local_business'] = true;
update_option( 'schema_ai_settings', $settings );
wp_send_json_success();
}
/**
* AJAX handler: manually trigger a full site sync to the SaaS dashboard.
*/
public function ajax_force_sync() {
check_ajax_referer( 'schema_ai_force_sync' );
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( array( 'message' => __( 'Permission denied.', 'ai-schema-gen' ) ) );
}
if ( ! class_exists( 'Schema_AI_Sync' ) ) {
wp_send_json_error( array( 'message' => __( 'Sync class not available.', 'ai-schema-gen' ) ) );
}
$sync = new Schema_AI_Sync();
$result = $sync->run_sync();
if ( $result ) {
wp_send_json_success( array( 'message' => __( 'Sync complete. Your dashboard will update shortly.', 'ai-schema-gen' ) ) );
} else {
wp_send_json_error( array( 'message' => __( 'Sync failed. Please check your API key and try again.', 'ai-schema-gen' ) ) );
}
}
/**
* Redirect to setup wizard on first activation.
*/
public function maybe_redirect_to_setup() {
if ( ! get_transient( 'schema_ai_activation_redirect' ) ) {
return;
}
delete_transient( 'schema_ai_activation_redirect' );
// Don't redirect on multisite activation or AJAX.
if ( is_network_admin() || isset( $_GET['activate-multi'] ) || wp_doing_ajax() ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended
return;
}
// Only redirect if setup not completed.
$settings = get_option( 'schema_ai_settings', array() );
if ( ! empty( $settings['api_key'] ) ) {
return;
}
wp_safe_redirect( admin_url( 'admin.php?page=ai-schema-gen-setup' ) );
exit;
}
/**
* Register the admin menu and submenu pages.
*
* Creates a top-level "Schema AI" menu with Dashboard, All Schemas,
* and Settings submenus.
*/
public function add_admin_menu() {
// Top-level menu.
add_menu_page(
__( 'AI Schema Gen', 'ai-schema-gen' ),
__( 'AI Schema Gen', 'ai-schema-gen' ),
'edit_posts',
'ai-schema-gen',
array( $this, 'display_dashboard_page' ),
'dashicons-editor-code',
30
);
// Dashboard submenu (replaces the default duplicate).
add_submenu_page(
'ai-schema-gen',
__( 'Dashboard', 'ai-schema-gen' ),
__( 'Dashboard', 'ai-schema-gen' ),
'edit_posts',
'ai-schema-gen',
array( $this, 'display_dashboard_page' )
);
// Schema Tester submenu.
add_submenu_page(
'ai-schema-gen',
__( 'Schema Tester', 'ai-schema-gen' ),
__( 'Schema Tester', 'ai-schema-gen' ),
'edit_posts',
'ai-schema-gen-tester',
array( $this, 'display_tester_page' )
);
// Settings submenu (requires manage_options).
add_submenu_page(
'ai-schema-gen',
__( 'Settings', 'ai-schema-gen' ),
__( 'Settings', 'ai-schema-gen' ),
'manage_options',
'ai-schema-gen-settings',
array( $this, 'display_settings_page' )
);
// Upgrade submenu.
add_submenu_page(
'ai-schema-gen',
__( 'Upgrade', 'ai-schema-gen' ),
__( 'Upgrade', 'ai-schema-gen' ),
'edit_posts',
'ai-schema-gen-upgrade',
array( $this, 'display_upgrade_page' )
);
// Schema Audit submenu.
add_submenu_page(
'ai-schema-gen',
__( 'Schema Audit', 'ai-schema-gen' ),
__( 'Schema Audit', 'ai-schema-gen' ),
'manage_options',
'ai-schema-gen-audit',
array( $this, 'display_audit_page' )
);
// Setup wizard (hidden from menu).
add_submenu_page(
null,
__( 'AI Schema Gen Setup', 'ai-schema-gen' ),
__( 'Setup', 'ai-schema-gen' ),
'manage_options',
'ai-schema-gen-setup',
array( $this, 'display_setup_page' )
);
}
/**
* Register the stylesheets for the admin area.
*
* Loads admin CSS only on Schema AI pages and post edit screens
* where the meta box is displayed.
*
* @param string $hook The current admin page hook suffix.
*/
public function enqueue_styles( $hook ) {
// Load on Schema AI admin pages.
$is_schema_ai_page = ( false !== strpos( $hook, 'ai-schema-gen' ) );
// Load on post edit screens for the meta box.
$is_edit_screen = in_array( $hook, array( 'post.php', 'post-new.php' ), true );
if ( ! $is_schema_ai_page && ! $is_edit_screen ) {
return;
}
wp_enqueue_style(
'ai-schema-gen-admin',
plugin_dir_url( __FILE__ ) . 'assets/css/admin.css',
array(),
$this->version
);
// Entity Authority repeater styles (settings page only).
if ( false !== strpos( $hook, 'ai-schema-gen-settings' ) ) {
wp_add_inline_style(
'ai-schema-gen-admin',
'.ai-schema-gen-repeater-row{display:flex;gap:8px;margin-bottom:8px;align-items:center;}' .
'.ai-schema-gen-repeater-row .regular-text{flex:1;margin:0;}' .
'.ai-schema-gen-remove-row{flex-shrink:0;line-height:1;padding:0 8px;font-size:16px;}'
);
}
// Setup wizard styles.
if ( false !== strpos( $hook, 'ai-schema-gen-setup' ) ) {
wp_enqueue_style(
'ai-schema-gen-setup-wizard',
plugin_dir_url( __FILE__ ) . 'assets/css/setup-wizard.css',
array(),
$this->version
);
}
}
/**
* Register the JavaScript for the admin area.
*
* Loads admin JS only on Schema AI pages and post edit screens.
* Localizes script with REST API data, user plan, and settings.
*
* @param string $hook The current admin page hook suffix.
*/
public function enqueue_scripts( $hook ) {
// Load on Schema AI admin pages.
$is_schema_ai_page = ( false !== strpos( $hook, 'ai-schema-gen' ) );
// Load on post edit screens for the meta box.
$is_edit_screen = in_array( $hook, array( 'post.php', 'post-new.php' ), true );
if ( ! $is_schema_ai_page && ! $is_edit_screen ) {
return;
}
// Load the WordPress media library on the settings page (for logo upload).
if ( false !== strpos( $hook, 'ai-schema-gen-settings' ) ) {
wp_enqueue_media();
}
wp_enqueue_script(
'ai-schema-gen-admin',
plugin_dir_url( __FILE__ ) . 'assets/js/build/index.js',
array( 'wp-element', 'wp-components', 'wp-i18n', 'wp-api-fetch' ),
$this->version,
true
);
$current_user_id = get_current_user_id();
$plan = get_user_meta( $current_user_id, 'schema_ai_plan', true );
// Get site logo for defaults.
$site_logo = '';
$custom_logo_id = get_theme_mod( 'custom_logo' );
if ( $custom_logo_id ) {
$logo_src = wp_get_attachment_image_src( $custom_logo_id, 'full' );
if ( $logo_src ) {
$site_logo = $logo_src[0];
}
}
// Settings page script.
if ( false !== strpos( $hook, 'ai-schema-gen-settings' ) ) {
wp_enqueue_script(
'ai-schema-gen-settings-page',
plugin_dir_url( __FILE__ ) . 'assets/js/settings-page.js',
array( 'jquery' ),
$this->version,
true
);
wp_localize_script(
'ai-schema-gen-settings-page',
'schemaAiSettings',
array(
'ajaxUrl' => esc_url( admin_url( 'admin-ajax.php' ) ),
'nonce' => wp_create_nonce( 'schema_ai_verify_key' ),
'syncNonce' => wp_create_nonce( 'schema_ai_force_sync' ),
'i18n' => array(
'enterApiKey' => __( 'Please enter an API key first.', 'ai-schema-gen' ),
'verifying' => __( 'Verifying...', 'ai-schema-gen' ),
'connected' => __( 'Connected successfully!', 'ai-schema-gen' ),
'connectionFailed' => __( 'Connection failed.', 'ai-schema-gen' ),
'connectionFailedRetry' => __( 'Connection failed. Please try again.', 'ai-schema-gen' ),
'selectLogo' => __( 'Select Organization Logo', 'ai-schema-gen' ),
'useLogo' => __( 'Use as Logo', 'ai-schema-gen' ),
'syncing' => __( 'Syncing...', 'ai-schema-gen' ),
'syncSuccess' => __( 'Sync complete. Dashboard will update shortly.', 'ai-schema-gen' ),
'syncFailed' => __( 'Sync failed. Check your API key and try again.', 'ai-schema-gen' ),
),
)
);
// Site type dropdown + entity authority repeater scripts.
$settings_raw = get_option( 'schema_ai_settings', array() );
wp_enqueue_script(
'ai-schema-gen-settings-site-type',
plugin_dir_url( __FILE__ ) . 'assets/js/settings-site-type.js',
array(),
$this->version,
true
);
wp_localize_script(
'ai-schema-gen-settings-site-type',
'schemaAISiteTypeData',
array(
'localSlugs' => class_exists( 'Schema_AI_Site_Type' ) ? Schema_AI_Site_Type::get_local_business_slugs() : array(),
'currentSiteType' => isset( $settings_raw['site_type'] ) ? $settings_raw['site_type'] : '',
)
);
wp_enqueue_script(
'ai-schema-gen-settings-repeater',
plugin_dir_url( __FILE__ ) . 'assets/js/settings-repeater.js',
array(),
$this->version,
true
);
}
// Setup wizard script.
if ( false !== strpos( $hook, 'ai-schema-gen-setup' ) ) {
wp_enqueue_script(
'ai-schema-gen-setup-wizard',
plugin_dir_url( __FILE__ ) . 'assets/js/setup-wizard.js',
array( 'jquery' ),
$this->version,
true
);
wp_localize_script(
'ai-schema-gen-setup-wizard',
'schemaAiWizard',
array(
'nonce' => wp_create_nonce( 'schema_ai_verify_key' ),
'i18n' => array(
'enterApiKey' => __( 'Please enter an API key.', 'ai-schema-gen' ),
'verifying' => __( 'Verifying...', 'ai-schema-gen' ),
'connected' => __( 'Connected!', 'ai-schema-gen' ),
'plan' => __( 'Plan:', 'ai-schema-gen' ),
'invalidKey' => __( 'Invalid API key. Please check and try again.', 'ai-schema-gen' ),
'couldNotVerify' => __( 'Could not verify. Check your API key or try again later.', 'ai-schema-gen' ),
'verifyKey' => __( 'Verify Key', 'ai-schema-gen' ),
),
)
);
}
// Setup wizard: business subtype dropdown.
if ( false !== strpos( $hook, 'ai-schema-gen-setup' ) ) {
wp_enqueue_script(
'ai-schema-gen-setup-wizard-subtype',
plugin_dir_url( __FILE__ ) . 'assets/js/setup-wizard-subtype.js',
array(),
$this->version,
true
);
wp_localize_script(
'ai-schema-gen-setup-wizard-subtype',
'schemaAIWizardData',
array(
'localSlugs' => class_exists( 'Schema_AI_Site_Type' ) ? Schema_AI_Site_Type::get_local_business_slugs() : array(),
)
);
}
// Schema audit page: per-row regenerate button.
if ( false !== strpos( $hook, 'ai-schema-gen-audit' ) ) {
wp_enqueue_script(
'ai-schema-gen-schema-audit',
plugin_dir_url( __FILE__ ) . 'assets/js/schema-audit.js',
array(),
$this->version,
true
);
wp_localize_script(
'ai-schema-gen-schema-audit',
'schemaAIAudit',
array(
'regenerating' => __( 'Regenerating…', 'ai-schema-gen' ),
'done' => __( 'Done — reload to verify', 'ai-schema-gen' ),
'failed' => __( 'Failed — retry', 'ai-schema-gen' ),
)
);
}
$entity_score = class_exists( 'Schema_AI_Entity_Settings' )
? Schema_AI_Entity_Settings::calculate_score()
: array( 'score' => 0, 'breakdown' => array(), 'missing' => array() );
wp_localize_script(
'ai-schema-gen-admin',
'schemaAiData',
array(
'restUrl' => esc_url( rest_url( 'ai-schema-gen/v1/' ) ),
'nonce' => wp_create_nonce( 'wp_rest' ),
'postId' => get_the_ID(),
'postUrl' => get_the_ID() ? esc_url( get_permalink( get_the_ID() ) ) : '',
'adminUrl' => esc_url( admin_url() ),
'upgradeUrl' => esc_url( admin_url( 'admin.php?page=ai-schema-gen-upgrade' ) ),
'pluginUrl' => esc_url( SCHEMA_AI_PLUGIN_URL ),
'version' => $this->version,
'plan' => ! empty( $plan ) ? $plan : 'free',
'settings' => get_option( 'schema_ai_settings', array() ),
'activationTime' => (int) get_option( 'schema_ai_activation_time', 0 ),
'dismissBannerNonce' => wp_create_nonce( 'schema_ai_dismiss_business_banner' ),
'entityScore' => $entity_score,
'wpDefaults' => array(
'siteName' => get_bloginfo( 'name' ),
'siteUrl' => home_url( '/' ),
'siteLogo' => $site_logo,
),
)
);
}
/**
* Register the schema meta box for post and page editors.
*
* Also adds the meta box for custom post types if configured in settings.
*/
public function add_schema_meta_box() {
$post_types = array( 'post', 'page' );
// Include additional post types from settings.
$settings = get_option( 'schema_ai_settings', array() );
if ( ! empty( $settings['auto_generate_post_types'] ) && is_array( $settings['auto_generate_post_types'] ) ) {
$post_types = array_unique( array_merge( $post_types, $settings['auto_generate_post_types'] ) );
}
foreach ( $post_types as $post_type ) {
add_meta_box(
'ai-schema-gen-meta-box',
__( 'AI Schema Gen', 'ai-schema-gen' ),
array( $this, 'render_meta_box' ),
$post_type,
'normal',
'high'
);
}
}
/**
* Render the content of the schema meta box.
*
* Outputs a nonce field and includes the meta-box partial template
* which provides the UI for schema generation and preview.
*
* @param WP_Post $post The current post object.
*/
public function render_meta_box( $post ) {
wp_nonce_field( 'schema_ai_meta_box', 'schema_ai_meta_box_nonce' );
include plugin_dir_path( __FILE__ ) . 'partials/meta-box.php';
}
/**
* Save the schema meta box data when a post is saved.
*
* Verifies the nonce, checks for autosave and permissions, then
* sanitizes and stores any schema data submitted from the meta box.
*
* @param int $post_id The post ID being saved.
* @param WP_Post $post The post object being saved.
*/
public function save_schema_meta_box( $post_id, $post ) {
// Verify nonce.
if ( ! isset( $_POST['schema_ai_meta_box_nonce'] ) ) {
return;
}
if ( ! wp_verify_nonce( sanitize_text_field( wp_unslash( $_POST['schema_ai_meta_box_nonce'] ) ), 'schema_ai_meta_box' ) ) {
return;
}
// Check for autosave.
if ( defined( 'DOING_AUTOSAVE' ) && DOING_AUTOSAVE ) {
return;
}
// Check user permissions.
$post_type_object = get_post_type_object( $post->post_type );
if ( ! $post_type_object ) {
return;
}
if ( ! current_user_can( $post_type_object->cap->edit_post, $post_id ) ) {
return;
}
// Check if schema data was submitted.
if ( ! isset( $_POST['schema_ai_data'] ) ) {
return;
}
$raw_data = sanitize_text_field( wp_unslash( $_POST['schema_ai_data'] ) );
// Validate that the data is valid JSON.
$schema_data = json_decode( $raw_data, true );
if ( json_last_error() !== JSON_ERROR_NONE || ! is_array( $schema_data ) ) {
return;
}
// Sanitize the schema data recursively.
$schema_data = $this->sanitize_schema_data( $schema_data );
// Save to the custom table.
global $wpdb;
$table = esc_sql( $wpdb->prefix . 'schema_ai_schemas' );
// Determine schema type.
$schema_type = isset( $schema_data['@type'] ) ? sanitize_text_field( $schema_data['@type'] ) : 'Unknown';
// Check if a schema already exists for this post.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Required for custom table; no WP API alternative exists.
$existing = $wpdb->get_var(
$wpdb->prepare(
// phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Table name is sanitized via esc_sql() and not user input.
"SELECT id FROM {$table} WHERE post_id = %d AND schema_type = %s LIMIT 1",
$post_id,
$schema_type
)
);
$json_data = wp_json_encode( $schema_data, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE );
if ( $existing ) {
// Update existing schema.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Required for custom table; write operation (UPDATE), caching not applicable.
$wpdb->update(
$table,
array(
'schema_data' => $json_data,
'validation_status' => 'pending',
'updated_at' => current_time( 'mysql' ),
),
array( 'id' => $existing ),
array( '%s', '%s', '%s' ),
array( '%d' )
);
} else {
// Insert new schema.
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Required for custom table; write operation (INSERT), caching not applicable.
$wpdb->insert(
$table,
array(
'post_id' => $post_id,
'schema_type' => $schema_type,
'schema_data' => $json_data,
'is_auto_generated' => 0,
'validation_status' => 'pending',
'created_at' => current_time( 'mysql' ),
'updated_at' => current_time( 'mysql' ),
),
array( '%d', '%s', '%s', '%d', '%s', '%s', '%s' )
);
}
// Invalidate the schema cache for this post.
if ( class_exists( 'Schema_AI_Schema_Cache' ) ) {
$cache = new Schema_AI_Schema_Cache();
$cache->invalidate( $post_id );
}
}
/**
* Display the Dashboard admin page.
*
* Outputs a wrapper div with a mount point for the React application.
*/
public function display_dashboard_page() {
echo '';
}
/**
* Display the Schema Tester admin page.
*/
public function display_tester_page() {
echo '';
}
/**
* Display the Upgrade admin page.
*/
public function display_upgrade_page() {
echo '';
}
/**
* Display the Schema Audit admin page.
*/
public function display_audit_page() {
include plugin_dir_path( __FILE__ ) . 'partials/schema-audit.php';
}
/**
* Display the Settings admin page.
*
* Includes the settings partial template which handles form display
* and submission processing.
*/
public function display_settings_page() {
include plugin_dir_path( __FILE__ ) . 'partials/settings-page.php';
}
/**
* Display the Setup Wizard page.
*/
public function display_setup_page() {
include plugin_dir_path( __FILE__ ) . 'partials/setup-wizard.php';
}
/**
* Add plugin action links on the Plugins page.
*
* Adds "Settings" and "Upgrade" links to the plugin row.
*
* @param array $links Existing plugin action links.
* @return array Modified plugin action links.
*/
public function add_action_links( $links ) {
$settings_link = sprintf(
'%s',
esc_url( admin_url( 'admin.php?page=ai-schema-gen-settings' ) ),
esc_html__( 'Settings', 'ai-schema-gen' )
);
$upgrade_link = sprintf(
'%s',
esc_url( admin_url( 'admin.php?page=ai-schema-gen-upgrade' ) ),
esc_html__( 'Upgrade', 'ai-schema-gen' )
);
array_unshift( $links, $settings_link, $upgrade_link );
return $links;
}
/**
* Add a "Generate Schema" button to the WordPress admin bar on singular pages.
*
* @param WP_Admin_Bar $wp_admin_bar The admin bar instance.
*/
public function add_admin_bar_generate( $wp_admin_bar ) {
if ( is_admin() || ! is_singular() || ! is_admin_bar_showing() ) {
return;
}
$post_id = get_queried_object_id();
if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
$wp_admin_bar->add_node(
array(
'id' => 'ai-schema-gen-generate',
'title' => '' . esc_html__( 'Generate Schema', 'ai-schema-gen' ) . '',
'href' => '#',
)
);
// Dropdown child: Test with Google Rich Results
$wp_admin_bar->add_node(
array(
'id' => 'ai-schema-gen-test-google',
'parent' => 'ai-schema-gen-generate',
'title' => '' . esc_html__( 'Test with Google', 'ai-schema-gen' ),
'href' => 'https://search.google.com/test/rich-results?url=' . rawurlencode( get_permalink( $post_id ) ),
'meta' => array(
'target' => '_blank',
'rel' => 'noopener noreferrer',
),
)
);
// Dropdown child: Explore Plugin (goes to the plugin dashboard on this site)
$wp_admin_bar->add_node(
array(
'id' => 'ai-schema-gen-explore',
'parent' => 'ai-schema-gen-generate',
'title' => '' . esc_html__( 'Explore Plugin', 'ai-schema-gen' ),
'href' => admin_url( 'admin.php?page=ai-schema-gen' ),
)
);
}
/**
* Enqueue inline script and styles for the admin bar Generate Schema button.
*/
public function enqueue_admin_bar_script() {
if ( is_admin() || ! is_singular() || ! is_admin_bar_showing() || ! is_user_logged_in() ) {
return;
}
$post_id = get_queried_object_id();
if ( ! $post_id || ! current_user_can( 'edit_post', $post_id ) ) {
return;
}
$js = sprintf(
'(function(){' .
'var btn=document.getElementById("wp-admin-bar-ai-schema-gen-generate");' .
'if(!btn)return;' .
'var link=btn.querySelector("a");' .
'if(!link)return;' .
'var orig=link.innerHTML;' .
'link.addEventListener("click",function(e){' .
'e.preventDefault();' .
'if(btn.classList.contains("ai-schema-gen-ab-loading"))return;' .
'btn.classList.add("ai-schema-gen-ab-loading");' .
'btn.classList.remove("ai-schema-gen-ab-success","ai-schema-gen-ab-error");' .
'link.innerHTML="Generating\u2026";' .
'fetch("%s",{' .
'method:"POST",' .
'headers:{"Content-Type":"application/json","X-WP-Nonce":"%s"},' .
'credentials:"same-origin",' .
'body:JSON.stringify({post_id:%d})' .
'}).then(function(r){return r.json().then(function(d){return{ok:r.ok,data:d}})})' .
'.then(function(res){' .
'btn.classList.remove("ai-schema-gen-ab-loading");' .
'if(res.ok&&res.data.success){' .
'btn.classList.add("ai-schema-gen-ab-success");' .
'link.innerHTML="Schema Generated";' .
'}else{' .
'btn.classList.add("ai-schema-gen-ab-error");' .
'link.innerHTML="Failed";' .
'}' .
'setTimeout(function(){' .
'btn.classList.remove("ai-schema-gen-ab-success","ai-schema-gen-ab-error");' .
'link.innerHTML=orig;' .
'},3000);' .
'}).catch(function(){' .
'btn.classList.remove("ai-schema-gen-ab-loading");' .
'btn.classList.add("ai-schema-gen-ab-error");' .
'link.innerHTML="Failed";' .
'setTimeout(function(){' .
'btn.classList.remove("ai-schema-gen-ab-error");' .
'link.innerHTML=orig;' .
'},3000);' .
'});' .
'});' .
'}());',
esc_url( rest_url( 'ai-schema-gen/v1/generate' ) ),
wp_create_nonce( 'wp_rest' ),
$post_id
);
wp_add_inline_script( 'admin-bar', $js );
$css = '#wp-admin-bar-ai-schema-gen-generate .ab-icon.dashicons{' .
'font-family:dashicons!important;font-size:20px;line-height:1;' .
'}' .
'#wp-admin-bar-ai-schema-gen-generate .ab-icon:before{' .
'content:"\f475";top:3px;' .
'}' .
'.ai-schema-gen-ab-loading .ab-icon.dashicons:before{' .
'content:"\f463"!important;animation:ai-schema-gen-spin 1s linear infinite;' .
'}' .
'.ai-schema-gen-ab-success #wp-admin-bar-ai-schema-gen-generate>a,' .
'#wp-admin-bar-ai-schema-gen-generate.ai-schema-gen-ab-success>a{color:#46b450!important;}' .
'.ai-schema-gen-ab-success .ab-icon.dashicons:before{content:"\f12a"!important;}' .
'.ai-schema-gen-ab-error #wp-admin-bar-ai-schema-gen-generate>a,' .
'#wp-admin-bar-ai-schema-gen-generate.ai-schema-gen-ab-error>a{color:#dc3232!important;}' .
'.ai-schema-gen-ab-error .ab-icon.dashicons:before{content:"\f534"!important;}' .
'@keyframes ai-schema-gen-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}';
wp_add_inline_style( 'admin-bar', $css );
}
/**
* Recursively sanitize schema data.
*
* Walks through the schema array and sanitizes all string values
* while preserving the data structure.
*
* @param array $data The schema data to sanitize.
* @return array The sanitized schema data.
*/
private function sanitize_schema_data( $data ) {
$sanitized = array();
foreach ( $data as $key => $value ) {
$key = sanitize_text_field( $key );
if ( is_array( $value ) ) {
$sanitized[ $key ] = $this->sanitize_schema_data( $value );
} elseif ( is_string( $value ) ) {
// Allow URLs to pass through with minimal sanitization.
if ( filter_var( $value, FILTER_VALIDATE_URL ) ) {
$sanitized[ $key ] = esc_url_raw( $value );
} else {
$sanitized[ $key ] = sanitize_text_field( $value );
}
} elseif ( is_numeric( $value ) ) {
$sanitized[ $key ] = $value;
} elseif ( is_bool( $value ) ) {
$sanitized[ $key ] = $value;
} else {
$sanitized[ $key ] = sanitize_text_field( (string) $value );
}
}
return $sanitized;
}
/**
* Remove all third-party admin notices on our plugin's pages.
*/
public function suppress_third_party_notices() {
// phpcs:ignore WordPress.Security.NonceVerification.Recommended -- Read-only check to identify the current admin page; no form data processed.
$page = isset( $_GET['page'] ) ? sanitize_key( wp_unslash( $_GET['page'] ) ) : '';
$our_pages = array(
'ai-schema-gen',
'ai-schema-gen-tester',
'ai-schema-gen-settings',
'ai-schema-gen-upgrade',
'ai-schema-gen-setup',
'ai-schema-gen-audit',
);
if ( in_array( $page, $our_pages, true ) ) {
remove_all_actions( 'admin_notices' );
remove_all_actions( 'all_admin_notices' );
remove_all_actions( 'user_admin_notices' );
}
}
}